Wikipedia testwiki https://test.wikipedia.org/wiki/Main_Page MediaWiki 1.46.0-wmf.26 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 Page487 0 49558 739860 372895 2026-04-30T09:32:08Z ~2026-23062-17 73557 /* */ 739860 wikitext text/x-wiki '''Page487''' This in test page No. 487 [https://djksjls.com] [[Category:Category with multiple files]] i4fs11v7r6w3xy846yitwktf6xqbsh5 739865 739860 2026-04-30T10:09:23Z ~2026-23062-17 73557 739865 wikitext text/x-wiki '''Page487''' This in test page No. 487 [https://djksjls.com] [[Category:Category with multiple files]] foobar ob8f8c5655qg4pq29j3ngqy2sjl0rvk MYL2 0 81403 739788 689659 2026-04-29T18:33:26Z ~2026-26101-46 73755 bad ref, who herd of these people? science folks or sumthih? 739788 wikitext text/x-wiki {{PBB|geneid=4633}} '''Myosin regulatory light chain 2, ventricular/cardiac muscle isoform''' (MLC-2) also known as the '''regulatory light chain of myosin''' (RLC) is a [[protein]] that in humans is encoded by the ''MYL2'' [[gene]].<ref name="pmid1386340">{{vcite2 journal | vauthors = Macera MJ, Szabo P, Wadgaonkar R, Siddiqui MA, Verma RS | title = Localization of the gene coding for ventricular myosin regulatory light chain (MYL2) to human chromosome 12q23-q24.3 | journal = Genomics | volume = 13 | issue = 3 | pages = 829–31 | date = Jul 1992 | pmid = 1386340 | pmc = | doi = 10.1016/0888-7543(92)90161-K }}</ref><ref name="entrez">{{cite web | title = Entrez Gene: MYL2 myosin, light chain 2, regulatory, cardiac, slow| url = http://www.ncbi.nlm.nih.gov/sites/entrez?Db=gene&Cmd=ShowDetailView&TermToSearch=4633| accessdate = }} Ventricular myosin light chain-2 (MLC-2v) refers to the ventricular cardiac muscle form of myosin light chain 2 (Myl2). MLC-2v is a 19-KDa protein composed of 166 amino acids, that belongs to the [[EF-hand]] Ca<sup>2+</sup> binding superfamily.<ref name = "Grabarek_2006">{{vcite2 journal | vauthors = Grabarek Z | title = Structural basis for diversity of the EF-hand calcium-binding proteins | journal = Journal of Molecular Biology | volume = 359 | issue = 3 | pages = 509-25 | date = Jun 2006 | pmid = 16678204 | doi = 10.1016/j.jmb.2006.03.066 }}</ref> MLC-2v interacts with the neck/tail region of the muscle thick filament protein myosin to regulate myosin motility and function.<ref name = "Rayment_1992b">{{vcite2 journal | vauthors = Rayment I, Holden HM, Whittaker M, Yohn CB, Lorenz M, Holmes KC, Milligan RA | title = Structure of the actin-myosin complex and its implications for muscle contraction | journal = Science | volume = 261 | issue = 5117 | pages = 58–65 | date = Jul 1993 | pmid = 8316858 | doi=10.1126/science.8316858}}</ref> == Structure == Eat more beef == Function == The N-terminal [[EF-hand]] domain of RLC binds calcium/magnesium at activating concentrations,<ref name="Morimoto_1974">{{vcite2 journal | vauthors = Morimoto K, Harrington WF | title = Evidence for structural changes in vertebrate thick filaments induced by calcium | journal = Journal of Molecular Biology | volume = 88 | issue = 3 | pages = 693–709 | date = Sep 1974 | pmid = 4449125 | doi = 10.1016/0022-2836(74)90417-3}}</ref> however the dissociation rate is too slow to modulate [[cardiac contractility]] on a beat-by-beat basis.<ref name="Bagshaw_1977">{{vcite2 journal | vauthors = Bagshaw CR | title = On the location of the divalent metal binding sites and the light chain subunits of vertebrate myosin | journal = Biochemistry | volume = 16 | issue = 1 | pages = 59–67 | year = 1977 | pmid = 188447 | doi = 10.1021/bi00620a010| url = }}</ref> Perturbing the calcium binding region of RLC through [[site-directed mutagenesis]] (D47A) decreased tension and stiffness in isolated, skinned skeletal muscle fibers,<ref name="Diffee_1996">{{vcite2 journal | vauthors = Diffee GM, Patel JR, Reinach FC, Greaser ML, Moss RL | title = Altered kinetics of contraction in skeletal muscle fibers containing a mutant myosin regulatory light chain with reduced divalent cation binding | journal = Biophysical Journal | volume = 71 | issue = 1 | pages = 341–50 | date = Jul 1996 | pmid = 8804617 | pmc = 1233485 | doi = 10.1016/S0006-3495(96)79231-7 }}</ref> suggesting that the conformational change induced by calcium binding to RLC is functionally important.<ref name="Szczesna_2001">{{vcite2 journal | vauthors = Szczesna D, Ghosh D, Li Q, Gomes AV, Guzman G, Arana C, Zhi G, Stull JT, Potter JD | title = Familial hypertrophic cardiomyopathy mutations in the regulatory light chains of myosin affect their structure, Ca2+ binding, and phosphorylation | journal = The Journal of Biological Chemistry | volume = 276 | issue = 10 | pages = 7086–92 | date = Mar 2001 | pmid = 11102452 | doi = 10.1074/jbc.M009823200 }}</ref> Pork fat is health fat, right? == Expression patterns during cardiac development == MLC-2v plays an essential role in early embryonic cardiac development and function.<ref name = "Chen_1998">{{vcite2 journal | vauthors = Chen J, Kubalak SW, Minamisawa S, Price RL, Becker KD, Hickey R, Ross J, Chien KR | title = Selective requirement of myosin light chain 2v in embryonic heart function | journal = The Journal of Biological Chemistry | volume = 273 | issue = 2 | pages = 1252–6 | date = Jan 1998 | pmid = 9422794 | doi=10.1074/jbc.273.2.1252}}</ref> and represents one of the earliest markers of ventricular specification.<ref name = "Chien_1993">{{vcite2 journal | vauthors = O'Brien TX, Lee KJ, Chien KR | title = Positional specification of ventricular myosin light chain 2 expression in the primitive murine heart tube | journal = Proceedings of the National Academy of Sciences of the United States of America | volume = 90 | issue = 11 | pages = 5157–61 | date = Jun 1993 | pmid = 8506363 | doi=10.1073/pnas.90.11.5157}}</ref> During early development (E7.5-8.0), MLC-2v is expressed within the cardiac crescent. The expression pattern of MLC-2v becomes restricted to the ventricular segment of the linear heart tube at E8.0 and remains restricted within the ventricle into adulthood.<ref name = "Chien_1993"/><ref>{{vcite2 journal | vauthors = Ross RS, Navankasattusas S, Harvey RP, Chien KR | title = An HF-1a/HF-1b/MEF-2 combinatorial element confers cardiac ventricular specificity and established an anterior-posterior gradient of expression | journal = Development | volume = 122 | issue = 6 | pages = 1799-809 | date = Jun 1996 | pmid = 8674419 }}</ref> == Phosphorylation sites and regulators == Recent studies have highlighted a critical role for MLC2v phosphorylation in cardiac torsion, function and disease.<ref name = "Sheikh_2014"/> In cardiac muscle, the critical phosphorylation sites have been identified as Ser14/Ser15 in the mouse heart and Ser15 in the human heart.<ref name = "Scruggs_2010">{{vcite2 journal | vauthors = Scruggs SB, Reisdorph R, Armstrong ML, Warren CM, Reisdorph N, Solaro RJ, Buttrick PM | title = A novel, in-solution separation of endogenous cardiac sarcomeric proteins and identification of distinct charged variants of regulatory light chain | journal = Molecular & Cellular Proteomics | volume = 9 | issue = 9 | pages = 1804-18 | date = Sep 2010 | pmid = 20445002 | doi = 10.1074/mcp.M110.000075 }}</ref> The major kinase responsible for MLC-2v phosphorylation has been identified as cardiac myosin light chain kinase (MLCK), encoded for by Mylk3.<ref name = "Scruggs_2010"/><ref name = "Seguchi_2007" >{{vcite2 journal | vauthors = Seguchi O, Takashima S, Yamazaki S, Asakura M, Asano Y, Shintani Y, Wakeno M, Minamino T, Kondo H, Furukawa H, Nakamaru K, Naito A, Takahashi T, Ohtsuka T, Kawakami K, Isomura T, Kitamura S, Tomoike H, Mochizuki N, Kitakaze M | title = A cardiac myosin light chain kinase regulates sarcomere assembly in the vertebrate heart | journal = The Journal of Clinical Investigation | volume = 117 | issue = 10 | pages = 2812-24 | date = Oct 2007 | pmid = 17885681 | doi = 10.1172/JCI30804 }}</ref> Loss of cardiac MLCK in mice results in loss of cardiac MLC-2v phosphorylation and cardiac abnormalities.<ref name="Ding_2010"/><ref name = "Warren_2012">{{vcite2 journal | vauthors = Warren SA, Briggs LE, Zeng H, Chuang J, Chang EI, Terada R, Li M, Swanson MS, Lecker SH, Willis MS, Spinale FG, Maupin-Furlowe J, McMullen JR, Moss RL, Kasahara H | title = Myosin light chain phosphorylation is critical for adaptation to cardiac stress | journal = Circulation | volume = 126 | issue = 22 | pages = 2575-88 | date = Nov 2012 | pmid = 23095280 | doi = 10.1161/CIRCULATIONAHA.112.116202 }}</ref> == Clinical significance == Mutations in MYL2 have been associated with familial [[hypertrophic cardiomyopathy]] (FHC). Ten [[hypertrophic cardiomyopathy|FHC]] mutations have been identified in RLC: E22K, A13T, N47K, P95A, F18L, R58Q, IVS6-1G>C, L103E, IVS5-2A>G, D166V. The first three-E22K, A13T and N47K-have been associated with an unusual mid-ventricular chamber obstruction type of hypertrophy.<ref name="Poetter_1996">{{vcite2 journal | vauthors = Poetter K, Jiang H, Hassanzadeh S, Master SR, Chang A, Dalakas MC, Rayment I, Sellers JR, Fananapazir L, Epstein ND | title = Mutations in either the essential or regulatory light chains of myosin are associated with a rare myopathy in human heart and skeletal muscle | journal = Nature Genetics | volume = 13 | issue = 1 | pages = 63–9 | date = May 1996 | pmid = 8673105 | doi = 10.1038/ng0596-63 }}</ref><ref name="Andersen_2001">{{vcite2 journal | vauthors = Andersen PS, Havndrup O, Bundgaard H, Moolman-Smook JC, Larsen LA, Mogensen J, Brink PA, Børglum AD, Corfield VA, Kjeldsen K, Vuust J, Christiansen M | title = Myosin light chain mutations in familial hypertrophic cardiomyopathy: phenotypic presentation and frequency in Danish and South African populations | journal = Journal of Medical Genetics | volume = 38 | issue = 12 | pages = E43 | date = Dec 2001 | pmid = 11748309 | pmc = 1734772 | doi = }}</ref> Three mutations-R58Q, D166V and IVS5-2-are associated with more malignant outcomes, manifesting with sudden cardiac death or at earlier ages.<ref name="Richard_2003">{{vcite2 journal | vauthors = Richard P, Charron P, Carrier L, Ledeuil C, Cheav T, Pichereau C, Benaiche A, Isnard R, Dubourg O, Burban M, Gueffet JP, Millaire A, Desnos M, Schwartz K, Hainque B, Komajda M | title = Hypertrophic cardiomyopathy: distribution of disease genes, spectrum of mutations, and implications for a molecular diagnosis strategy | journal = Circulation | volume = 107 | issue = 17 | pages = 2227–32 | date = May 2003 | pmid = 12707239 | doi = 10.1161/01.CIR.0000066323.15244.54 }}</ref><ref name="Flavigny_1998">{{vcite2 journal | vauthors = Flavigny J, Richard P, Isnard R, Carrier L, Charron P, Bonne G, Forissier JF, Desnos M, Dubourg O, Komajda M, Schwartz K, Hainque B | title = Identification of two novel mutations in the ventricular regulatory myosin light chain gene (MYL2) associated with familial and classical forms of hypertrophic cardiomyopathy | journal = Journal of Molecular Medicine | volume = 76 | issue = 3-4 | pages = 208–14 | date = Mar 1998 | pmid = 9535554 | doi = 10.1007/s001090050210}}</ref><ref name="Kabaeva_2002">{{vcite2 journal | vauthors = Kabaeva ZT, Perrot A, Wolter B, Dietz R, Cardim N, Correia JM, Schulte HD, Aldashev AA, Mirrakhimov MM, Osterziel KJ | title = Systematic analysis of the regulatory and essential myosin light chain genes: genetic variants and mutations in hypertrophic cardiomyopathy | journal = European Journal of Human Genetics | volume = 10 | issue = 11 | pages = 741–8 | date = Nov 2002 | pmid = 12404107 | doi = 10.1038/sj.ejhg.5200872 }}</ref><ref name="Mörner_2003">{{vcite2 journal | vauthors = Mörner S, Richard P, Kazzam E, Hellman U, Hainque B, Schwartz K, Waldenström A | title = Identification of the genotypes causing hypertrophic cardiomyopathy in northern Sweden | journal = Journal of Molecular and Cellular Cardiology | volume = 35 | issue = 7 | pages = 841–9 | date = Jul 2003 | pmid = 12818575 | doi = 10.1016/s0022-2828(03)00146-9}}</ref> Functional studies demonstrate that FHC mutations in RLC affect its ability to both be phosphorylated and to bind calcium/magnesium.<ref name="Harris_2011">{{vcite2 journal | vauthors = Harris SP, Lyons RG, Bezold KL | title = In the thick of it: HCM-causing mutations in myosin binding proteins of the thick filament | journal = Circulation Research | volume = 108 | issue = 6 | pages = 751–64 | date = Mar 2011 | pmid = 21415409 | doi = 10.1161/CIRCRESAHA.110.231670 }}</ref> === Effects on cardiac muscle contraction === MLC-2v plays an important role in cross-bridge cycling kinetics and cardiac muscle contraction.<ref name = "Sheikh_2012">{{vcite2 journal | vauthors = Sheikh F, Ouyang K, Campbell SG, Lyon RC, Chuang J, Fitzsimons D, Tangney J, Hidalgo CG, Chung CS, Cheng H, Dalton ND, Gu Y, Kasahara H, Ghassemian M, Omens JH, Peterson KL, Granzier HL, Moss RL, McCulloch AD, Chen J | title = Mouse and computational models link Mlc2v dephosphorylation to altered myosin kinetics in early cardiac disease | journal = The Journal of Clinical Investigation | volume = 122 | issue = 4 | pages = 1209-21 | date = Apr 2012 | pmid = 22426213 | doi = 10.1172/JCI61134 }}</ref> MLC-2v phosphorylation at Ser14 and Ser15 increases myosin lever arm stiffness and promotes myosin head diffusion, which altogether slow down myosin kinetics and prolong the duty cycle as a means to fine-tune myofilament Ca2+ sensitivity to force.<ref name = "Sheikh_2012"/> === Effects on adult cardiac torsion, function and disease === A gradient in the levels of both MLC2v phosphorylation and its kinase, cardiac MLCK, has been shown to exist across the human heart from endocardium (low phosphorylation) to epicardium (high phosphorylation).<ref name="Davis_2001">{{vcite2 journal | vauthors = Davis JS, Hassanzadeh S, Winitsky S, Lin H, Satorius C, Vemuri R, Aletras AH, Wen H, Epstein ND | title = The overall pattern of cardiac contraction depends on a spatial gradient of myosin regulatory light chain phosphorylation | journal = Cell | volume = 107 | issue = 5 | pages = 631–41 | date = Nov 2001 | pmid = 11733062 | doi=10.1016/s0092-8674(01)00586-4}}</ref> The existence of this gradient has been proposed to impact cardiac torsion due to the relative spatial orientation of endocardial versus epicardial myofibers.<ref name="Davis_2001"/> In support of this, recent studies have shown that MLC-2v phosphorylation is critical in regulating left ventricular torsion.<ref name = "Warren_2012"/><ref name = "Sheikh_2012"/> Variations in myosin cycling kinetics and contractile properties as a result of differential MLC-2v phosphorylation (Ser14/15) influence both epicardial and endocardial myofiber tension development and recovery to control cardiac torsion and myofiber strain mechanics.<ref name = "Warren_2012"/<ref name = "Sheikh_2012"/> A number of human studies have implicated loss of MLC-2v phosphorylation in the pathogenesis of human dilated cardiomyopathy and heart failure.<ref name = "Scruggs_2010"/><ref name = "Morano_1992" >{{vcite2 journal | vauthors = Morano I | title = Effects of different expression and posttranslational modifications of myosin light chains on contractility of skinned human cardiac fibers | journal = Basic Research in Cardiology | volume = 87 Suppl 1 | pages = 129–41 | date = 1992 | pmid = 1386730 | doi=10.1007/978-3-642-72474-9_11}}</ref><ref name= "van_Der_Velden_2001">{{vcite2 journal | vauthors = van Der Velden J, Klein LJ, Zaremba R, Boontje NM, Huybregts MA, Stooker W, Eijsman L, de Jong JW, Visser CA, Visser FC, Stienen GJ | title = Effects of calcium, inorganic phosphate, and pH on isometric force in single skinned cardiomyocytes from donor and failing human hearts | journal = Circulation | volume = 104 | issue = 10 | pages = 1140–6 | date = Sep 2001 | pmid = 11535570 | doi=10.1161/hc3501.095485}}</ref><ref name = "van_Der_Velden_2003a">{{vcite2 journal | vauthors = van der Velden J, Papp Z, Zaremba R, Boontje NM, de Jong JW, Owen VJ, Burton PB, Goldmann P, Jaquet K, Stienen GJ | title = Increased Ca2+-sensitivity of the contractile apparatus in end-stage human heart failure results from altered phosphorylation of contractile proteins | journal = Cardiovascular Research | volume = 57 | issue = 1 | pages = 37–47 | date = Jan 2003 | pmid = 12504812 | doi=10.1016/s0008-6363(02)00606-5}}</ref><ref name = "van_Der_Velden_2003b">{{vcite2 journal | vauthors = van der Velden J, Papp Z, Boontje NM, Zaremba R, de Jong JW, Janssen PM, Hasenfuss G, Stienen GJ | title = The effect of myosin light chain 2 dephosphorylation on Ca2+ -sensitivity of force is enhanced in failing human hearts | journal = Cardiovascular Research | volume = 57 | issue = 2 | pages = 505–14 | date = Feb 2003 | pmid = 12566123 | doi=10.1016/s0008-6363(02)00662-4}}</ref> MLC-2v dephosphorylation has also been reported in human patients carrying a rare form of familial hypertrophic cardiomyopathy (FHC) based on specific MLC-2v and MLCK mutations.<ref name= "Szczesna_2001"/><ref name="Davis_2001"/><ref name = "Jacques_2008" >{{vcite2 journal | vauthors = Jacques AM, Briceno N, Messer AE, Gallon CE, Jalilzadeh S, Garcia E, Kikonda-Kanda G, Goddard J, Harding SE, Watkins H, Esteban MT, Tsang VT, McKenna WJ, Marston SB | title = The molecular phenotype of human cardiac myosin associated with hypertrophic obstructive cardiomyopathy | journal = Cardiovascular Research | volume = 79 | issue = 3 | pages = 481-91 | date = Aug 2008 | pmid = 18411228 | doi = 10.1093/cvr/cvn094 }}</ref> == Animal studies == MLC-2v plays a key role in the regulation of cardiac muscle contraction, through its interactions with myosin.<ref name = "Sheikh_2014" >{{vcite2 journal | vauthors = Sheikh F, Lyon RC, Chen J | title = Getting the skinny on thick filament regulation in cardiac muscle biology and disease | journal = Trends in Cardiovascular Medicine | volume = 24 | issue = 4 | pages = 133-41 | date = May 2014 | pmid = 23968570 | doi = 10.1016/j.tcm.2013.07.004 }}</ref> Loss of MLC-2v in mice is associated with ultrastructural defects in sarcomere assembly and results in dilated cardiomyopathy and heart failure with reduced ejection fraction, leading to embryonic lethality at E12.5.<ref name = "Chen_1998"/> More recently, a mutation in zebrafish tell tale heart (telm225) that encodes MLC-2, demonstrated that cardiac MLC-2 is required for thick filament stabilization and contractility in the embryonic zebrafish heart.<ref name = "Rottbauer_2006">{{vcite2 journal | vauthors = Rottbauer W, Wessels G, Dahme T, Just S, Trano N, Hassel D, Burns CG, Katus HA, Fishman MC | title = Cardiac myosin light chain-2: a novel essential component of thick-myofilament assembly and contractility of the heart | journal = Circulation Research | volume = 99 | issue = 3 | pages = 323-31 | date = Aug 2006 | pmid = 16809551 | doi = 10.1161/01.RES.0000234807.16034.fe }}</ref> The role of Myl2 mutations in pathogenesis has been determined through the generation of a number of mouse models.<ref name = "Sheikh_2012"/><ref name = "Abraham_2009">{{vcite2 journal | vauthors = Abraham TP, Jones M, Kazmierczak K, Liang HY, Pinheiro AC, Wagg CS, Lopaschuk GD, Szczesna-Cordary D | title = Diastolic dysfunction in familial hypertrophic cardiomyopathy transgenic model mice | journal = Cardiovascular Research | volume = 82 | issue = 1 | pages = 84-92 | date = Apr 2009 | pmid = 19150977 | doi = 10.1093/cvr/cvp016 }}</ref><ref name = "Muthu _2012">{{vcite2 journal | vauthors = Muthu P, Kazmierczak K, Jones M, Szczesna-Cordary D | title = The effect of myosin RLC phosphorylation in normal and cardiomyopathic mouse hearts | journal = Journal of Cellular and Molecular Medicine | volume = 16 | issue = 4 | pages = 911-9 | date = Apr 2012 | pmid = 21696541 | doi = 10.1111/j.1582-4934.2011.01371.x }}</ref> Transgenic mice overexpressing the human MLC-2v R58Q mutation, which is associated with FHC has been shown to lead to a reduction in MLC-2v phosphorylation in hearts.<ref name = "Abraham_2009"/> These mice exhibited features of FHC, including diastolic dysfunction that progressed with age.<ref name = "Abraham_2009"/> Similarly, cardiac overexpression of another FHC-associated MLC-2v mutation (D166V) results in loss of MLC-2v phosphorylation in mouse hearts.<ref name = "Muthu _2012"/> In addition to these findings, MLC-2v dephosphorylation in mice results in cardiac dilatation and dysfunction associated with features reminiscent of dilated cardiomyopathy, leading to heart failure and premature death.<ref name = "Mizutani_2010"/><ref name = "Warren_2012"/><ref name = "Sheikh_2012"/> Altogether these studies highlight a role for MLC-2v phosphorylation in adult heart function. These studies also suggest that torsion defects might be an early manifestation of dilated cardiomyopathy consequent to loss of MLC-2v phosphorylation.<ref name = "Sheikh_2012"/> MLC-2v also plays an important role in cardiac stress associated with hypertrophy.<ref name = "Warren_2012"/><ref name = "Sheikh_2012"/> In a novel MLC2v Ser14Ala/Ser15Ala knockin mouse model, complete loss of MLC2v (Ser14/Ser15) phosphorylation led to a worsened and differential (eccentric as opposed to concentric) response to pressure overload-induced hypertrophy.<ref name = "Sheikh_2012"/> In addition, mice lacking cardiac MLCK display heart failure and experience premature death in response to both pressure overload and swimming induced hypertrophy.<ref name = "Warren_2012"/> Consistent with these findings, a cardiac-specific transgenic mouse model overexpressing cardiac MLCK attenuated the response to cardiac hypertrophy induced by pressure overload.<ref name = "Warren_2012"/> Furthermore, in a cardiac-specific transgenic mouse model overexpressing skeletal myosin light chain kinase, the response to cardiac hypertrophy induced by treadmill exercise or isoproterenol was also attenuated.<ref name = "Huang_2008">{{vcite2 journal | vauthors = Huang J, Shelton JM, Richardson JA, Kamm KE, Stull JT | title = Myosin regulatory light chain phosphorylation attenuates cardiac hypertrophy | journal = The Journal of Biological Chemistry | volume = 283 | issue = 28 | pages = 19748-56 | date = Jul 2008 | pmid = 18474588 | doi = 10.1074/jbc.M802605200 }}</ref> These studies further highlight the therapeutic potential of increasing MLC-2v phosphorylation in settings of cardiac pathological stress. == References == {{reflist|33em}} == External links == * [http://www.heartproteome.org/copa/ProteinInfo.aspx?QType=Protein%20ID&QValue=Q6IB42 Mass spectrometry characterization of human MYL2 at COPaKB] {{Webarchive|url=https://web.archive.org/web/20150924025559/http://www.heartproteome.org/copa/ProteinInfo.aspx?QType=Protein%20ID&QValue=Q6IB42 |date=2015-09-24 }} <ref>{{cite PMID | 23965338}}</ref> * [http://www.ncbi.nlm.nih.gov/bookshelf/br.fcgi?book=gene&part=hyper-card GeneReviews/NIH/NCBI/UW entry on Familial Hypertrophic Cardiomyopathy Overview] *[http://cmkb.cellmigration.org/report.cgi?report=orth_overview&gene_id=4633 MYL2] {{Webarchive|url=https://web.archive.org/web/20110722014038/http://cmkb.cellmigration.org/report.cgi?report=orth_overview&gene_id=4633 |date=2011-07-22 }} Info with links in the [http://www.cellmigration.org/index.shtml Cell Migration Gateway] {{Webarchive|url=https://web.archive.org/web/20141211173306/http://www.cellmigration.org/index.shtml |date=2014-12-11 }} rt9eex4kp1px0impkmrqqcdr9xptm7k 739789 739788 2026-04-29T18:34:16Z ~2026-26101-46 73755 739789 wikitext text/x-wiki {{PBB|geneid=4633}} '''Myosin regulatory light chain 2, ventricular/cardiac muscle isoform''' (MLC-2) also known as the '''regulatory light chain of myosin''' (RLC) is a [[protein]] that in humans is encoded by the ''MYL2'' [[gene]].<ref name="pmid1386340">{{vcite2 journal | vauthors = Macera MJ, Szabo P, Wadgaonkar R, Siddiqui MA, Verma RS | title = Localization of the gene coding for ventricular myosin regulatory light chain (MYL2) to human chromosome 12q23-q24.3 | journal = Genomics | volume = 13 | issue = 3 | pages = 829–31 | date = Jul 1992 | pmid = 1386340 | pmc = | doi = 10.1016/0888-7543(92)90161-K }}</ref><ref name="entrez">{{cite web | title = Entrez Gene: MYL2 myosin, light chain 2, regulatory, cardiac, slow| url = http://www.ncbi.nlm.nih.gov/sites/entrez?Db=gene&Cmd=ShowDetailView&TermToSearch=4633| accessdate = }} Ventricular myosin light chain-2 (MLC-2v) refers to the ventricular cardiac muscle form of myosin light chain 2 (Myl2). MLC-2v is a 19-KDa protein composed of 166 amino acids, that belongs to the [[EF-hand]] Ca<sup>2+</sup> binding superfamily.<ref name = "Grabarek_2006">{{vcite2 journal | vauthors = Grabarek Z | title = Structural basis for diversity of the EF-hand calcium-binding proteins | journal = Journal of Molecular Biology | volume = 359 | issue = 3 | pages = 509-25 | date = Jun 2006 | pmid = 16678204 | doi = 10.1016/j.jmb.2006.03.066 }}</ref> MLC-2v interacts with the neck/tail region of the muscle thick filament protein myosin to regulate myosin motility and function.<ref name = "Rayment_1992b">{{vcite2 journal | vauthors = Rayment I, Holden HM, Whittaker M, Yohn CB, Lorenz M, Holmes KC, Milligan RA | title = Structure of the actin-myosin complex and its implications for muscle contraction | journal = Science | volume = 261 | issue = 5117 | pages = 58–65 | date = Jul 1993 | pmid = 8316858 | doi=10.1126/science.8316858}}</ref> == Structure == Eat more beef == Function == The N-terminal [[EF-hand]] domain of RLC binds calcium/magnesium at activating concentrations,<ref name="Morimoto_1974">{{vcite2 journal | vauthors = Morimoto K, Harrington WF | title = Evidence for structural changes in vertebrate thick filaments induced by calcium | journal = Journal of Molecular Biology | volume = 88 | issue = 3 | pages = 693–709 | date = Sep 1974 | pmid = 4449125 | doi = 10.1016/0022-2836(74)90417-3}}</ref> however the dissociation rate is too slow to modulate [[cardiac contractility]] on a beat-by-beat basis.<ref name="Bagshaw_1977">{{vcite2 journal | vauthors = Bagshaw CR | title = On the location of the divalent metal binding sites and the light chain subunits of vertebrate myosin | journal = Biochemistry | volume = 16 | issue = 1 | pages = 59–67 | year = 1977 | pmid = 188447 | doi = 10.1021/bi00620a010| url = }}</ref> Perturbing the calcium binding region of RLC through [[site-directed mutagenesis]] (D47A) decreased tension and stiffness in isolated, skinned skeletal muscle fibers,<ref name="Diffee_1996">{{vcite2 journal | vauthors = Diffee GM, Patel JR, Reinach FC, Greaser ML, Moss RL | title = Altered kinetics of contraction in skeletal muscle fibers containing a mutant myosin regulatory light chain with reduced divalent cation binding | journal = Biophysical Journal | volume = 71 | issue = 1 | pages = 341–50 | date = Jul 1996 | pmid = 8804617 | pmc = 1233485 | doi = 10.1016/S0006-3495(96)79231-7 }}</ref> suggesting that the conformational change induced by calcium binding to RLC is functionally important.<ref name="Szczesna_2001">{{vcite2 journal | vauthors = Szczesna D, Ghosh D, Li Q, Gomes AV, Guzman G, Arana C, Zhi G, Stull JT, Potter JD | title = Familial hypertrophic cardiomyopathy mutations in the regulatory light chains of myosin affect their structure, Ca2+ binding, and phosphorylation | journal = The Journal of Biological Chemistry | volume = 276 | issue = 10 | pages = 7086–92 | date = Mar 2001 | pmid = 11102452 | doi = 10.1074/jbc.M009823200 }}</ref> Pork fat is health fat, right? == Expression patterns during cardiac development == MLC-2v plays an essential role in early embryonic cardiac development and function.<ref name = "Chen_1998">{{vcite2 journal | vauthors = Chen J, Kubalak SW, Minamisawa S, Price RL, Becker KD, Hickey R, Ross J, Chien KR | title = Selective requirement of myosin light chain 2v in embryonic heart function | journal = The Journal of Biological Chemistry | volume = 273 | issue = 2 | pages = 1252–6 | date = Jan 1998 | pmid = 9422794 | doi=10.1074/jbc.273.2.1252}}</ref> and represents one of the earliest markers of ventricular specification.<ref name = "Chien_1993">{{vcite2 journal | vauthors = O'Brien TX, Lee KJ, Chien KR | title = Positional specification of ventricular myosin light chain 2 expression in the primitive murine heart tube | journal = Proceedings of the National Academy of Sciences of the United States of America | volume = 90 | issue = 11 | pages = 5157–61 | date = Jun 1993 | pmid = 8506363 | doi=10.1073/pnas.90.11.5157}}</ref> During early development (E7.5-8.0), MLC-2v is expressed within the cardiac crescent. The expression pattern of MLC-2v becomes restricted to the ventricular segment of the linear heart tube at E8.0 and remains restricted within the ventricle into adulthood.<ref name = "Chien_1993"/><ref>{{vcite2 journal | vauthors = Ross RS, Navankasattusas S, Harvey RP, Chien KR | title = An HF-1a/HF-1b/MEF-2 combinatorial element confers cardiac ventricular specificity and established an anterior-posterior gradient of expression | journal = Development | volume = 122 | issue = 6 | pages = 1799-809 | date = Jun 1996 | pmid = 8674419 }}</ref> == Phosphorylation sites and regulators == Recent studies have highlighted a critical role for MLC2v phosphorylation in cardiac torsion, function and disease.<ref name = "Sheikh_2014"/> In cardiac muscle, the critical phosphorylation sites have been identified as Ser14/Ser15 in the mouse heart and Ser15 in the human heart.<ref name = "Scruggs_2010">{{vcite2 journal | vauthors = Scruggs SB, Reisdorph R, Armstrong ML, Warren CM, Reisdorph N, Solaro RJ, Buttrick PM | title = A novel, in-solution separation of endogenous cardiac sarcomeric proteins and identification of distinct charged variants of regulatory light chain | journal = Molecular & Cellular Proteomics | volume = 9 | issue = 9 | pages = 1804-18 | date = Sep 2010 | pmid = 20445002 | doi = 10.1074/mcp.M110.000075 }}</ref> The major kinase responsible for MLC-2v phosphorylation has been identified as cardiac myosin light chain kinase (MLCK), encoded for by Mylk3.<ref name = "Scruggs_2010"/><ref name = "Seguchi_2007" >{{vcite2 journal | vauthors = Seguchi O, Takashima S, Yamazaki S, Asakura M, Asano Y, Shintani Y, Wakeno M, Minamino T, Kondo H, Furukawa H, Nakamaru K, Naito A, Takahashi T, Ohtsuka T, Kawakami K, Isomura T, Kitamura S, Tomoike H, Mochizuki N, Kitakaze M | title = A cardiac myosin light chain kinase regulates sarcomere assembly in the vertebrate heart | journal = The Journal of Clinical Investigation | volume = 117 | issue = 10 | pages = 2812-24 | date = Oct 2007 | pmid = 17885681 | doi = 10.1172/JCI30804 }}</ref> Loss of cardiac MLCK in mice results in loss of cardiac MLC-2v phosphorylation and cardiac abnormalities.<ref name="Ding_2010"/><ref name = "Warren_2012">{{vcite2 journal | vauthors = Warren SA, Briggs LE, Zeng H, Chuang J, Chang EI, Terada R, Li M, Swanson MS, Lecker SH, Willis MS, Spinale FG, Maupin-Furlowe J, McMullen JR, Moss RL, Kasahara H | title = Myosin light chain phosphorylation is critical for adaptation to cardiac stress | journal = Circulation | volume = 126 | issue = 22 | pages = 2575-88 | date = Nov 2012 | pmid = 23095280 | doi = 10.1161/CIRCULATIONAHA.112.116202 }}</ref> == Clinical significance == Mutations in MYL2 have been associated with familial [[hypertrophic cardiomyopathy]] (FHC). Ten [[hypertrophic cardiomyopathy|FHC]] mutations have been identified in RLC: E22K, A13T, N47K, P95A, F18L, R58Q, IVS6-1G>C, L103E, IVS5-2A>G, D166V. The first three-E22K, A13T and N47K-have been associated with an unusual mid-ventricular chamber obstruction type of hypertrophy.<ref name="Poetter_1996">{{vcite2 journal | vauthors = Poetter K, Jiang H, Hassanzadeh S, Master SR, Chang A, Dalakas MC, Rayment I, Sellers JR, Fananapazir L, Epstein ND | title = Mutations in either the essential or regulatory light chains of myosin are associated with a rare myopathy in human heart and skeletal muscle | journal = Nature Genetics | volume = 13 | issue = 1 | pages = 63–9 | date = May 1996 | pmid = 8673105 | doi = 10.1038/ng0596-63 }}</ref><ref name="Andersen_2001">{{vcite2 journal | vauthors = Andersen PS, Havndrup O, Bundgaard H, Moolman-Smook JC, Larsen LA, Mogensen J, Brink PA, Børglum AD, Corfield VA, Kjeldsen K, Vuust J, Christiansen M | title = Myosin light chain mutations in familial hypertrophic cardiomyopathy: phenotypic presentation and frequency in Danish and South African populations | journal = Journal of Medical Genetics | volume = 38 | issue = 12 | pages = E43 | date = Dec 2001 | pmid = 11748309 | pmc = 1734772 | doi = }}</ref> Three mutations-R58Q, D166V and IVS5-2-are associated with more malignant outcomes, manifesting with sudden cardiac death or at earlier ages.<ref name="Richard_2003">{{vcite2 journal | vauthors = Richard P, Charron P, Carrier L, Ledeuil C, Cheav T, Pichereau C, Benaiche A, Isnard R, Dubourg O, Burban M, Gueffet JP, Millaire A, Desnos M, Schwartz K, Hainque B, Komajda M | title = Hypertrophic cardiomyopathy: distribution of disease genes, spectrum of mutations, and implications for a molecular diagnosis strategy | journal = Circulation | volume = 107 | issue = 17 | pages = 2227–32 | date = May 2003 | pmid = 12707239 | doi = 10.1161/01.CIR.0000066323.15244.54 }}</ref><ref name="Flavigny_1998">{{vcite2 journal | vauthors = Flavigny J, Richard P, Isnard R, Carrier L, Charron P, Bonne G, Forissier JF, Desnos M, Dubourg O, Komajda M, Schwartz K, Hainque B | title = Identification of two novel mutations in the ventricular regulatory myosin light chain gene (MYL2) associated with familial and classical forms of hypertrophic cardiomyopathy | journal = Journal of Molecular Medicine | volume = 76 | issue = 3-4 | pages = 208–14 | date = Mar 1998 | pmid = 9535554 | doi = 10.1007/s001090050210}}</ref><ref name="Kabaeva_2002">{{vcite2 journal | vauthors = Kabaeva ZT, Perrot A, Wolter B, Dietz R, Cardim N, Correia JM, Schulte HD, Aldashev AA, Mirrakhimov MM, Osterziel KJ | title = Systematic analysis of the regulatory and essential myosin light chain genes: genetic variants and mutations in hypertrophic cardiomyopathy | journal = European Journal of Human Genetics | volume = 10 | issue = 11 | pages = 741–8 | date = Nov 2002 | pmid = 12404107 | doi = 10.1038/sj.ejhg.5200872 }}</ref><ref name="Mörner_2003">{{vcite2 journal | vauthors = Mörner S, Richard P, Kazzam E, Hellman U, Hainque B, Schwartz K, Waldenström A | title = Identification of the genotypes causing hypertrophic cardiomyopathy in northern Sweden | journal = Journal of Molecular and Cellular Cardiology | volume = 35 | issue = 7 | pages = 841–9 | date = Jul 2003 | pmid = 12818575 | doi = 10.1016/s0022-2828(03)00146-9}}</ref> Functional studies demonstrate that FHC mutations in RLC affect its ability to both be phosphorylated and to bind calcium/magnesium.<ref name="Harris_2011">{{vcite2 journal | vauthors = Harris SP, Lyons RG, Bezold KL | title = In the thick of it: HCM-causing mutations in myosin binding proteins of the thick filament | journal = Circulation Research | volume = 108 | issue = 6 | pages = 751–64 | date = Mar 2011 | pmid = 21415409 | doi = 10.1161/CIRCRESAHA.110.231670 }}</ref> === Effects on cardiac muscle contraction === MLC-2v plays an important role in cross-bridge cycling kinetics and cardiac muscle contraction.<ref name = "Sheikh_2012">{{vcite2 journal | vauthors = Sheikh F, Ouyang K, Campbell SG, Lyon RC, Chuang J, Fitzsimons D, Tangney J, Hidalgo CG, Chung CS, Cheng H, Dalton ND, Gu Y, Kasahara H, Ghassemian M, Omens JH, Peterson KL, Granzier HL, Moss RL, McCulloch AD, Chen J | title = Mouse and computational models link Mlc2v dephosphorylation to altered myosin kinetics in early cardiac disease | journal = The Journal of Clinical Investigation | volume = 122 | issue = 4 | pages = 1209-21 | date = Apr 2012 | pmid = 22426213 | doi = 10.1172/JCI61134 }}</ref> MLC-2v phosphorylation at Ser14 and Ser15 increases myosin lever arm stiffness and promotes myosin head diffusion, which altogether slow down myosin kinetics and prolong the duty cycle as a means to fine-tune myofilament Ca2+ sensitivity to force.<ref name = "Sheikh_2012"/> === Effects on adult cardiac torsion, function and disease === == References == {{reflist|33em}} == External links == * [http://www.heartproteome.org/copa/ProteinInfo.aspx?QType=Protein%20ID&QValue=Q6IB42 Mass spectrometry characterization of human MYL2 at COPaKB] {{Webarchive|url=https://web.archive.org/web/20150924025559/http://www.heartproteome.org/copa/ProteinInfo.aspx?QType=Protein%20ID&QValue=Q6IB42 |date=2015-09-24 }} <ref>{{cite PMID | 23965338}}</ref> * [http://www.ncbi.nlm.nih.gov/bookshelf/br.fcgi?book=gene&part=hyper-card GeneReviews/NIH/NCBI/UW entry on Familial Hypertrophic Cardiomyopathy Overview] *[http://cmkb.cellmigration.org/report.cgi?report=orth_overview&gene_id=4633 MYL2] {{Webarchive|url=https://web.archive.org/web/20110722014038/http://cmkb.cellmigration.org/report.cgi?report=orth_overview&gene_id=4633 |date=2011-07-22 }} Info with links in the [http://www.cellmigration.org/index.shtml Cell Migration Gateway] {{Webarchive|url=https://web.archive.org/web/20141211173306/http://www.cellmigration.org/index.shtml |date=2014-12-11 }} leti1wks3fx7qt8dbtb7d9v6n48dw89 Wikipédia:Administradores/Pedidos de aprovação/!SilentTest/7 0 91158 739776 690778 2026-04-29T14:48:22Z ~2026-23062-17 73557 /* */ 739776 wikitext text/x-wiki [https://duckduckgo.com] Pedido cancelado [[User:!Silent|!Silent]] ([[User talk:!Silent|talk]]) 01:19, 1 July 2016 (UTC) <!-- Terminada a votação, inserir o resumo de fechamento. --> == [[Usuário:!SilentTest|!SilentTest]] == * {{usuário|!SilentTest}} Testes :::'''<small>Aceitação do usuário:</small>''' === Perguntas === <!-- Para responder, apague toda a linha em que está escrito "Responda aqui". Não é obrigatório responder às questões. --> ;1. Em que tarefas usuais costuma participar? <!-- Responda aqui --> ;2. Que ferramentas administrativas pensa serem particularmente úteis? <!-- Responda aqui --> ;3. Já teve algum conflito com outro(s) usuário(s)? Se sim, foi resolvido? <!-- Responda aqui --> ;4. Já mediou alguma disputa? Se sim, qual o desfecho? <!-- Responda aqui --> ;5. Em sua opinião, que políticas da Wikipédia lusófona poderiam ser aperfeiçoadas? <!-- Responda aqui --> ;6. Quais as suas áreas gerais de interesse na Wikipédia? Poderia indicar alguns artigos em que tenha tido uma contribuição significativa? <!-- Responda aqui --> === Votação === ==== A favor ==== # ==== Contra ==== # ==== Abstenções ==== # === Comentários === {{Wikipédia:Administradores/Pedidos de aprovação/Concluídos/Rodapé}} ltdom62sadt3kbvv352xiyuu60i1aki User:Sam Sailor/test.js 2 98186 739773 739727 2026-04-29T14:11:05Z Sam Sailor 26820 Test 739773 javascript text/javascript //<nowiki> (function() { if (window.Headmaster) return; const VERSION = "0.9.2.1"; const defaultNS = [0, 118]; const userNS = window.headmaster_ns || []; const config = { menu: window.headmaster_menu || 'p-cactions', enableSummary: window.headmaster_do_summary !== false, customSummary: window.headmaster_summary || "", allowedNamespaces: Array.from(new Set([...defaultNS, ...userNS])), autoScan: window.headmaster_auto_scan !== false, autoRun: window.headmaster_auto_run === true }; const Headmaster = { VERSION: VERSION, DefaultSummary: "Converting [[MOS:PSEUDOHEAD|pseudo-headings]] {details}using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]. Semicolon markup is reserved for [[MOS:DLIST|description lists]].", styleInjected: false, css: ` #hm-panel { clear: both; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; margin-bottom: 1.5em; border-radius: 2px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } #hm-table { width: 100%; border-collapse: collapse; background: white; margin-top: 10px; } #hm-table th { background: #f2f2f2; border: 1px solid #a2a9b1; padding: 8px; text-align: left; } #hm-table td { border: 1px solid #a2a9b1; padding: 8px; vertical-align: middle; } .hm-context { font-family: 'Courier New', monospace; font-size: 0.9em; color: #54595d; background: #fdfdfd; } #hm-controls { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; } #hm-controls button { margin-left: 10px; padding: 6px 16px; cursor: pointer; border-radius: 2px; } .hm-btn-apply { background: #36c; color: white; border: 1px solid #36c; font-weight: bold; } .hm-btn-apply:hover { background: #447ff5; } .hm-global-selector { font-size: 0.9em; color: #202122; background: #eaecf0; padding: 8px; border-radius: 2px; border: 1px solid #a2a9b1; } .hm-global-selector a { font-weight: bold; text-decoration: none; color: #36c; } .hm-global-selector a:hover { text-decoration: underline; } .hm-locate-btn { cursor: pointer; color: #36c; border: 1px solid #a2a9b1; background: #f8f9fa; padding: 2px 6px; font-size: 0.85em; border-radius: 2px; } .hm-locate-btn:hover { background: #fff; border-color: #36c; } `, setup() { if (!config.allowedNamespaces.includes(mw.config.get("wgNamespaceNumber"))) return; const action = mw.config.get("wgAction"); this.addPortlet(action); if (action === 'view' && (config.autoScan || config.autoRun)) { this.silentScan(); } if (mw.util.getParamValue('headmaster') === '1' && ['edit', 'submit'].includes(action)) { const params = new URLSearchParams(window.location.search); params.delete('headmaster'); const searchString = params.toString(); const newUrl = window.location.pathname + (searchString ? '?' + searchString : ''); window.history.replaceState(null, '', newUrl); this.init(); } }, addPortlet(action) { const link = mw.util.addPortletLink(config.menu, '#', 'Headmaster', 'ca-hm', 'Interactively convert pseudo-headings'); if (link) { $(link).on('click', (e) => { e.preventDefault(); if (action === 'view') { this.redirectToEdit(); } else if (['edit', 'submit'].includes(action)) { this.init(); } }); } }, redirectToEdit() { window.location.href = mw.util.getUrl(mw.config.get("wgPageName"), { action: 'edit', headmaster: '1' }); }, silentScan() { new mw.Api().get({ action: 'query', prop: 'revisions', titles: mw.config.get('wgPageName'), rvprop: 'content', rvlimit: 1, formatversion: 2 }).done((data) => { const page = data.query.pages[0]; if (!page || !page.revisions) return; const wikitext = page.revisions[0].content; const tasks = this.analyzeText(wikitext); if (tasks.length > 0) { if (config.autoRun) { this.redirectToEdit(); } else { this.notifyDiscovery(tasks.length); } } }).fail((err) => { mw.log.warn("Headmaster: silentScan failed", err); }); }, notifyDiscovery(count) { const $msg = $('<span>').text(`Headmaster: Found ${count} issues. `).append($('<a>').text('Run conversion').css({ fontWeight: 'bold', cursor: 'pointer' }).on('click', () => this.redirectToEdit())); mw.notify($msg, { tag: 'hm-discovery', autoHide: false }); }, init() { if (!this.styleInjected) { mw.loader.addStyleTag(this.css); this.styleInjected = true; } const $textbox = $('#wpTextbox1'); const wikitext = $textbox.textSelection('getContents'); if (!wikitext) { mw.notify("Textbox is empty or not found.", { type: 'error' }); return; } const tasks = this.analyzeText(wikitext); if (tasks.length === 0) { mw.notify("No issues found."); return; } this.showUI(tasks, wikitext); }, analyzeText(wikitext) { const lines = wikitext.split("\n"); const tasks = []; let currentDepth = 1; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const headingMatch = line.match(/^(={2,6})\s*(.+?)\s*\1\s*$/); if (headingMatch) { currentDepth = headingMatch[1].length; const prevLine = i > 0 ? lines[i - 1].trim() : null; const isSmashed = prevLine !== null && prevLine !== ""; const isExtraSpace = i > 1 && lines[i - 1].trim() === "" && lines[i - 2].trim() === ""; if (isSmashed || isExtraSpace) { tasks.push({ index: i, type: 'WHITESPACE', original: lines[i], isSmashed: isSmashed, isExtraSpace: isExtraSpace }); } } else if (line.startsWith(';') && !line.startsWith(';;')) { const nextLine = lines[i + 1] || ""; if (!nextLine.trim().startsWith(':')) { const cleanText = line.substring(1).replace(/\s*:.*$/, "").trim(); const suggestedLevel = "=".repeat(Math.min(currentDepth + 1, 6)); tasks.push({ index: i, type: 'PSEUDO', original: line, cleanText: cleanText, context: nextLine.substring(0, 60).trim() + (nextLine.length > 60 ? "..." : ""), suggestedLevel: suggestedLevel }); } } } return tasks; }, showUI(tasks, originalWikitext) { $('#hm-panel').remove(); $('#editform').hide(); const visibleTasks = tasks.filter(t => t.type === 'PSEUDO'); const pseudoCount = visibleTasks.length; const whitespaceCount = tasks.filter(t => t.type === 'WHITESPACE').length; let statusMsg = ""; const s = (n) => n !== 1 ? 's' : ''; if (pseudoCount > 0) { statusMsg = `Found <strong>${pseudoCount}</strong> possible pseudo-heading${s(pseudoCount)}.`; if (whitespaceCount > 0) { statusMsg += ` Also fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } } else { statusMsg = `Did not find any pseudo-headings, but fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } const tableRows = pseudoCount > 0 ? visibleTasks.map((task, i) => ` <tr data-task-index="${task.index}"> <td><button class="hm-locate-btn" data-hm-i="${tasks.indexOf(task)}" title="Show in editor">Locate</button></td> <td class="hm-context"><code>${mw.html.escape(task.original)}</code></td> <td><label><input type="radio" name="hm-opt-${i}" value="keep"> Keep</label></td> <td><label><input type="radio" name="hm-opt-${i}" value="bold" checked> <b>Bold</b></label></td> <td><label><input type="radio" name="hm-opt-${i}" value="head"> Heading (${task.suggestedLevel.length})</label></td> <td class="hm-context">${mw.html.escape(task.context || "(end of page)")}</td> </tr> `).join("") : `<tr><td colspan="6" style="text-align:center; padding: 2em; color: #54595d; background: #f8f9fa;"> No interactive heading changes detected. Only background spacing normalization will be applied. </td></tr>`; const $panel = $(` <div id="hm-panel"> <h3 style="margin-top:0">Headmaster v${this.VERSION}: Review pseudo-headings</h3> <div class="hm-global-selector" ${pseudoCount === 0 ? 'style="display:none"' : ''}> <strong>Bulk action:</strong> <a href="#" id="hm-all-keep">Set all to Keep</a> | <a href="#" id="hm-all-bold">Set all to Bold</a> | <a href="#" id="hm-all-head">Set all to Heading</a> </div> <table id="hm-table"> <thead> <tr> <th>Find</th> <th>Source</th> <th colspan="3">Action</th> <th>Next line context</th> </tr> </thead> <tbody>${tableRows}</tbody> </table> <div id="hm-controls"> <span>${statusMsg}</span> <div> <button id="hm-cancel" class="mw-ui-button">Cancel</button> <button id="hm-apply" class="hm-btn-apply">Show changes (diff)</button> </div> </div> </div> `); $('#content').prepend($panel); window.scrollTo(0, 0); $('.hm-locate-btn').on('click', function() { $(document).off('mousedown.hmlocate'); const idx = $(this).data('hm-i'); const task = tasks[idx]; const lines = originalWikitext.split('\n'); let charOffset = 0; for (let j = 0; j < task.index; j++) { charOffset += lines[j].length + 1; } $('#hm-panel').hide(); $('#editform').show(); const $textbox = $('#wpTextbox1'); $textbox.textSelection('setSelection', { start: charOffset, end: charOffset + lines[task.index].length }); $textbox.textSelection('scrollToCaretPosition'); const locateNotify = mw.notify("Locating line " + (task.index + 1) + ". Click outside the editor to return.", { tag: 'hm-locate', type: 'success', autoHide: false }); setTimeout(() => { $(document).on('mousedown.hmlocate', function(e) { if (!$(e.target).closest('#editform, .mw-notification-area').length) { $(document).off('mousedown.hmlocate'); Promise.resolve(locateNotify).then(n => n.close()); $('#editform').hide(); $('#hm-panel').show(); } }); }, 200); }); $('#hm-all-keep').on('click', (e) => { e.preventDefault(); $('input[value="keep"]').prop('checked', true); }); $('#hm-all-bold').on('click', (e) => { e.preventDefault(); $('input[value="bold"]').prop('checked', true); }); $('#hm-all-head').on('click', (e) => { e.preventDefault(); $('input[value="head"]').prop('checked', true); }); $('#hm-cancel').on('click', () => { $('#hm-panel').remove(); $('#editform').show(); }); $('#hm-apply').on('click', () => this.applyChanges(tasks, originalWikitext)); }, getProcessedSummary(existingSummary, bolds, heads, spaces) { if (!config.enableSummary) return existingSummary; let mySummary = ""; let isDefault = false; if (config.customSummary) { mySummary = config.customSummary; } else { isDefault = true; if (bolds > 0 || heads > 0) { const detailsArr = []; if (bolds > 0) detailsArr.push(`${bolds} to bold`); if (heads > 0) detailsArr.push(`${heads} to heading`); const detailsStr = detailsArr.length > 0 ? `(${detailsArr.join(', ')}) ` : ""; mySummary = this.DefaultSummary.replace("{details}", detailsStr); if (spaces > 0) { mySummary += " Also handled [[MOS:BLANKLINE]]."; } } else if (spaces > 0) { mySummary = "Handling [[MOS:BLANKLINE]] using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]."; } } if (!mySummary) return existingSummary; const trimmedOld = existingSummary.trim(); if (!trimmedOld) return mySummary; if (/[.!?]$/.test(trimmedOld)) { return trimmedOld + " " + mySummary; } else { const finalSummary = isDefault ? mySummary.charAt(0).toLowerCase() + mySummary.slice(1) : mySummary; return trimmedOld + "; " + finalSummary; } }, applyChanges(tasks, wikitext) { try { let lines = wikitext.split("\n"); let bCount = 0, hCount = 0, wCount = 0; const pseudoTasks = tasks.filter(t => t.type === 'PSEUDO'); for (let i = pseudoTasks.length - 1; i >= 0; i--) { const task = pseudoTasks[i]; const idx = task.index; const action = $('#hm-table tbody tr').filter(`[data-task-index="${idx}"]`).find('input:checked').val(); if (action === 'bold') { let newValue = `'''${task.cleanText}'''`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; bCount++; } else if (action === 'head') { const h = task.suggestedLevel; let newValue = `${h} ${task.cleanText} ${h}`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; hCount++; } } const spaceTasks = tasks.filter(t => t.type === 'WHITESPACE'); for (let i = spaceTasks.length - 1; i >= 0; i--) { const task = spaceTasks[i]; const idx = task.index; if (task.isSmashed) { lines.splice(idx, 0, ""); } else if (task.isExtraSpace) { let backIdx = idx - 1; while (backIdx > 0 && lines[backIdx].trim() === "" && lines[backIdx - 1].trim() === "") { lines.splice(backIdx, 1); backIdx--; } } wCount++; } if (bCount + hCount + wCount === 0) { mw.notify("No changes selected."); $('#hm-panel').remove(); $('#editform').show(); return; } $('#wpTextbox1').textSelection('setContents', lines.join("\n")); const $summary = $('#wpSummary'); $summary.val(this.getProcessedSummary($summary.val(), bCount, hCount, wCount)); $('#hm-panel').remove(); $('#editform').show(); mw.notify(`Headmaster: Fixed ${wCount} spacing issue(s) and converted ${bCount + hCount} heading(s).`); $('html, body').animate({ scrollTop: 0 }, 'fast'); $('#wpDiff').click(); } catch (err) { $('#editform').show(); mw.notify("Headmaster error: " + err.message, { type: 'error' }); } } }; window.Headmaster = Headmaster; mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => { $(() => Headmaster.setup()); }); })(); /* global ComradeLib, mw, $ */ (async function() { await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-lib.js&action=raw&ctype=text/javascript'); async function getTargetPage(api, name, type) { const titles = type === 'surname' ? [`List of people with the English surname ${name}`, `List of people with surname ${name}`, `${name} (surname)`, `${name} (name)`, name] : [`List of people with given name ${name}`, `List of people named ${name}`, `${name} (given name)`, `${name} (name)`, name]; const res = await api.get({ action: 'query', titles: titles.join('|'), formatversion: 2 }); if (!res.query || !res.query.pages) return null; return titles.find(t => { const pg = res.query.pages.find(p => p.title === t); return pg && !pg.missing; }); } async function processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean) { const { givens, surnames } = await fetchWikidataNameLabels(entity); const nameParts = tClean.split(' '); const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const primarySurname = hasFamilyNameHatnote ? nameParts[0] : (getSmartSortKey(tClean, entity).split(',')[0] || nameParts[nameParts.length - 1]); for (const name of givens) { if (name.toLowerCase() === primarySurname.toLowerCase()) continue; const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'given'); if (target) await verifyAndNudge(api, name, 'given name', currentTitle, hasShortDesc, isOrphan, target); } const finalSurnames = surnames.length > 0 ? surnames : (nameParts.length >= 2 ? [nameParts[nameParts.length - 1]] : []); for (const name of finalSurnames) { const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'surname'); if (target) await verifyAndNudge(api, name, 'surname', currentTitle, hasShortDesc, isOrphan, target); } } async function verifyAndNudge(api, name, type, currentTitle, hasShortDesc, isOrphan, foundTitle) { const res = await api.get({ action: 'query', prop: 'revisions', titles: foundTitle, rvprop: 'content', formatversion: 2 }); const page = res.query.pages[0]; if (!page || page.missing || !page.revisions) return; const text = page.revisions[0].content; const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text); const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text); const isNamePageTitle = foundTitle.includes('List of people with'); if (!isNameMatch && !isNameDab && !isNamePageTitle) return; const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]'); const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text); if (alreadyListed) return; const $container = $('<div>').addClass('comrade-container').addClass(isOrphan ? 'comrade-nudge' : 'comrade-info'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text(isOrphan ? 'Comrade nudge:' : 'Informative:')); const $content = $('<div>').addClass('comrade-content'); $content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({ href: mw.util.getUrl(foundTitle), target: '_blank' }).text(foundTitle), "; 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('Consider adding ', $('<code>').text(snippet)); const $copyBtn = $('<button>').text('Copy & insert').on('click', function() { navigator.clipboard.writeText('\n' + snippet).then(() => { $(this).text('Copied!').prop('disabled', true); const editUrl = mw.util.getUrl(foundTitle, { action: 'edit', summary: 'Adding * {{[[Template:Annotated biography link|anbl]]|[[' + currentTitle + ']]}}' }); window.open(editUrl, '_blank'); }); }); $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); ComradeLib.appendDismiss($container); } async function checkNamingPrecision(currentTitle, isHuman) { const disambigMatch = currentTitle.match(/^(.+?)(?:, | \()/); if (isHuman || !disambigMatch) return; const baseTitle = disambigMatch[1].trim(); const api = new mw.Api(); try { const baseRes = await api.get({ action: 'query', titles: baseTitle, redirects: 1, formatversion: 2 }); const page = baseRes.query.pages[0]; const baseExists = !page.missing; let baseRedirectsToSelf = false; if (baseRes.query.redirects) { baseRedirectsToSelf = baseRes.query.redirects.some(r => r.to === currentTitle); } const searchRes = await api.get({ action: 'query', list: 'allpages', apprefix: baseTitle, aplimit: 50, formatversion: 2 }); const competitors = searchRes.query.allpages.filter(p => { const t = p.title; return t !== currentTitle && t !== baseTitle && (t.startsWith(baseTitle + ', ') || t.startsWith(baseTitle + ' (')); }); if (!baseExists || baseRedirectsToSelf) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').addClass('comrade-content'); if (competitors.length === 0) { $header.append($('<strong>').text('Precision:')); const reason = baseRedirectsToSelf ? "already redirects here" : "is free"; $content.append($('<span>').append(`The base name `, $('<code>').text(baseTitle), ` ${reason}. Consider moving this article.`)); const moveUrl = mw.util.getUrl('Special:MovePage/' + currentTitle, { wpNewTitle: baseTitle, wpReason: 'Unnecessary disambiguation; base name is free/redirects here per [[WP:PRECISION]]' }); $content.append($('<button>').text(`Move to ${baseTitle}`).on('click', () => window.open(moveUrl, '_blank'))); } else { $header.append($('<strong>').text('Observation:')); const state = baseRedirectsToSelf ? 'redirect to this page' : 'redlink'; $content.append($('<span>').append(`Base name `, $('<code>').text(baseTitle), ` has ${competitors.length} competitors, but it is currently a ${state}.`)); $content.append($('<span>').text('Consider if a disambiguation page is needed.')); } $container.append($header, $content); ComradeLib.appendDismiss($container); } } catch (e) { console.warn("Comrade: Precision check failed", e); } } async function checkHumanSort(wikitext, entity, currentTitle, tClean, refName) { const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i); const { givens } = await fetchWikidataNameLabels(entity); if (dsMatch) { const currentDS = dsMatch[1].trim(); if (currentDS.includes(',')) { let [lastPart, firstPart] = currentDS.split(',').map(s => s.trim()); if (firstPart.toLowerCase().endsWith(lastPart.toLowerCase())) { let fixedFirst = firstPart.substring(0, firstPart.length - lastPart.length).trim(); let suggestion = `${fixNameCasing(lastPart, refName)}, ${fixNameCasing(fixedFirst, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, "Redundant surname in given name field."); } else if (givens.length > 0 && lastPart.split(' ').length > 1) { const parts = lastPart.split(' '); const misplacedGiven = parts.find(part => givens.some(gn => gn.toLowerCase() === part.toLowerCase())); if (misplacedGiven) { const newSurname = parts.filter(p => p !== misplacedGiven).join(' '); let suggestion = `${fixNameCasing(newSurname, refName)}, ${fixNameCasing(misplacedGiven + ' ' + firstPart, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, `Wikidata suggests "${misplacedGiven}" is a given name.`); } } } } else { const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const nameParts = tClean.split(' '); const targetSortName = hasFamilyNameHatnote ? `${nameParts[0]}, ${nameParts.slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { const $dsMissing = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Add: {{DEFAULTSORT:${targetSortName}}}`).on('click', () => performSortCorrection(targetSortName, "add")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Tag is missing. `, $btn)); $dsMissing.append($header); ComradeLib.appendDismiss($dsMissing); } } } async function fetchWikidataEntity(qid) { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: qid, props: 'claims|labels', languages: 'en', format: 'json', origin: '*' }); try { const wikiData = await fetch(url).then(r => r.json()); return wikiData.entities[qid]; } catch (e) { return null; } } function getSmartSortKey(fullName, entity) { const countryIds = entity.claims?.P27?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const locationIds = entity.claims?.P17?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const placeIds = [...(entity.claims?.P19 || []), ...(entity.claims?.P119 || [])].map(c => c.mainsnak?.datavalue?.value?.id).filter(Boolean); const allRelevantIds = [...countryIds, ...locationIds, ...placeIds]; const birthYear = parseInt(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.match(/[+-](\d{4})/)?.[1]) || null; if (fullName.startsWith("Saint ")) return fullName.replace("Saint ", ""); const arabicParticles = /\b(Abu|Abd|Abdel|Abdul|ben|bin|bint)\b/i; if (fullName.match(arabicParticles)) { if (birthYear && birthYear > 1900) { const parts = fullName.split(' '); const binIndex = parts.findIndex(p => p.toLowerCase() === 'bin' || p.toLowerCase() === 'ben'); if (binIndex > 0) return parts.slice(binIndex).join(' ') + ', ' + parts.slice(0, binIndex).join(' '); } else { return fullName; } } const eastAsianQids = ["Q148", "Q865", "Q884", "Q424", "Q17", "Q881"]; if (allRelevantIds.some(id => eastAsianQids.includes(id))) { if (allRelevantIds.includes("Q17") && birthYear && birthYear > 1885) return westernSort(fullName); const parts = fullName.split(' '); if (parts.length > 1) return `${parts[0]}, ${parts.slice(1).join(' ')}`; } return westernSort(fullName); } function westernSort(name) { let cleanName = name; if (name.startsWith("O'")) cleanName = name.replace("O'", "O"); const parts = cleanName.split(' '); if (parts.length < 2) return cleanName; const surname = parts.pop(); if (["Jr.", "Jr", "III", "II", "Sr.", "Sr"].includes(surname)) { const actualSurname = parts.pop(); return `${actualSurname}, ${parts.join(' ')} ${surname}`; } return `${surname}, ${parts.join(' ')}`; } async function performSortCorrection(newName, type) { const api = new mw.Api(); const title = mw.config.get("wgPageName"); try { const data = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = data.query.pages[0].revisions[0].content; const dsRegex = /\{\{\s*(?:DEFAULTSORT|Defaultsort)\s*:[^}]+\}\}/i; const newTag = `{{DEFAULTSORT:${newName}}}`; if (dsRegex.test(content)) { content = content.replace(dsRegex, newTag); } else { const catRegex = /\[\[Category:/i; content = catRegex.test(content) ? content.replace(catRegex, `${newTag}\n[[Category:`) : content.trim() + `\n\n${newTag}`; } await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Setting DEFAULTSORT to ${newName}`, nocreate: true }); mw.notify(`DEFAULTSORT ${type === "add" ? "added" : "corrected"} to ${newName}!`, { title: 'Comrade Success', type: 'success' }); setTimeout(() => location.reload(), 700); } catch (err) { mw.notify('Failed to save DEFAULTSORT.', { type: 'error' }); } } function stripDiacritics(str) { return str.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); } function renderSortNudge(target, reason) { const $dsNudge = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Correct to: ${target}`).on('click', () => performSortCorrection(target, "format")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` ${reason} `, $btn)); $dsNudge.append($header); ComradeLib.appendDismiss($dsNudge); } async function fetchWikidataNameLabels(entity) { const familyIds = entity.claims?.P734?.map(c => c.mainsnak.datavalue.value.id) || []; const givenIds = entity.claims?.P735?.map(c => c.mainsnak.datavalue.value.id) || []; const allNameIds = [...new Set([...familyIds, ...givenIds])]; if (allNameIds.length === 0) return { givens: [], surnames: [] }; const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: allNameIds.join('|'), props: 'labels', languages: 'en', format: 'json', origin: '*' }); const nameData = await fetch(url).then(r => r.json()); return { givens: givenIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean), surnames: familyIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean) }; } function fixNameCasing(part, referenceName) { return part.split(' ').map(word => { const match = referenceName.match(new RegExp(`\\b${word}\\b`, 'i')); if (match) return match[0]; return referenceName.split(' ').find(tWord => word.toLowerCase().startsWith(tWord.toLowerCase()) || tWord.toLowerCase().startsWith(word.toLowerCase())) || word; }).join(' '); } async function init() { const api = new mw.Api(); const ns = mw.config.get("wgNamespaceNumber"); const action = mw.config.get("wgAction"); if (mw.config.get("wgDBname") !== "enwiki" || action !== "view") return; if (ns === 14) { const $btn = $('<button>').attr('id', 'comrade-audit-btn').text('Audit category quality').css({ 'margin-bottom': '10px', 'padding': '5px 10px', 'cursor': 'pointer' }).on('click', ComradeLib.performCategoryAudit); $('.mw-category-generated').prepend($btn); return; } if (ns !== 0) return; const qid = mw.config.get("wgWikibaseItemId"); const currentTitle = mw.config.get("wgTitle"); 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; // Diacritic and domain checks const cleanTitle = stripDiacritics(currentTitle); if (cleanTitle !== currentTitle) await ComradeLib.checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic"); await ComradeLib.checkDomainRedirect(qid, currentTitle); // Orphan logic 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 $btn = $('<button>').text('Remove orphan tag').on('click', async () => await ComradeLib.performDeorphan()); const $statusText = $('<span>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn); $container.append($('<div>').addClass('comrade-header').append($statusText)); ComradeLib.appendDismiss($container); } // Wikidata/human logic let entity = qid ? await fetchWikidataEntity(qid) : null; let isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5"); await checkNamingPrecision(currentTitle, isHuman); if (isHuman && entity) { const tClean = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim(); const refName = entity.labels?.en?.value || currentTitle; const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext); const isOrphan = isOrphanTagged && backLinkCount < 1; await ComradeLib.checkShortDescDates(wikitext, entity); // Check DEFAULTSORT await checkHumanSort(wikitext, entity, currentTitle, tClean, refName); const longName = ComradeLib.getLongNameFromWikitext(wikitext); if (longName && longName.length > tClean.length) { let sortKey = hasFamilyNameHatnote ? `${longName.split(' ')[0]}, ${longName.split(' ').slice(1).join(' ')}` : getSmartSortKey(longName, entity); const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`; await ComradeLib.checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext); } // Sort name R const targetSortName = hasFamilyNameHatnote ? `${tClean.split(' ')[0]}, ${tClean.split(' ').slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName && targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { let targetPage = currentTitle.includes(' (') ? currentTitle.split(' (')[0] : currentTitle; let rCat = targetPage !== currentTitle ? "R from ambiguous sort name" : "R from sort name"; if (targetSortName.includes(',')) { const p = targetSortName.split(','); rCat += `|${p[0].trim().charAt(0).toUpperCase()}|${p[1].trim().charAt(0).toUpperCase()}`; } await ComradeLib.checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name"); } // Name lists await processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean); } // Quality/ORES const oresWait = setInterval(async () => { if ($('.article_quality').length) { clearInterval(oresWait); await ComradeLib.checkQualityConsistency(); } }, 500); setTimeout(() => clearInterval(oresWait), 5000); } catch (err) { console.error("Comrade error:", err); } } init(); })(); //</nowiki> o18hpo468ygirqeiwgtghrghgaalvyo 739821 739773 2026-04-30T04:45:32Z Sam Sailor 26820 Test 739821 javascript text/javascript //<nowiki> (function() { if (window.Headmaster) return; const VERSION = "0.9.2.1"; const defaultNS = [0, 118]; const userNS = window.headmaster_ns || []; const config = { menu: window.headmaster_menu || 'p-cactions', enableSummary: window.headmaster_do_summary !== false, customSummary: window.headmaster_summary || "", allowedNamespaces: Array.from(new Set([...defaultNS, ...userNS])), autoScan: window.headmaster_auto_scan !== false, autoRun: window.headmaster_auto_run === false }; const Headmaster = { VERSION: VERSION, DefaultSummary: "Converting [[MOS:PSEUDOHEAD|pseudo-headings]] {details}using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]. Semicolon markup is reserved for [[MOS:DLIST|description lists]].", styleInjected: false, css: ` #hm-panel { clear: both; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; margin-bottom: 1.5em; border-radius: 2px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } #hm-table { width: 100%; border-collapse: collapse; background: white; margin-top: 10px; } #hm-table th { background: #f2f2f2; border: 1px solid #a2a9b1; padding: 8px; text-align: left; } #hm-table td { border: 1px solid #a2a9b1; padding: 8px; vertical-align: middle; } .hm-context { font-family: 'Courier New', monospace; font-size: 0.9em; color: #54595d; background: #fdfdfd; } #hm-controls { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; } #hm-controls button { margin-left: 10px; padding: 6px 16px; cursor: pointer; border-radius: 2px; } .hm-btn-apply { background: #36c; color: white; border: 1px solid #36c; font-weight: bold; } .hm-btn-apply:hover { background: #447ff5; } .hm-global-selector { font-size: 0.9em; color: #202122; background: #eaecf0; padding: 8px; border-radius: 2px; border: 1px solid #a2a9b1; } .hm-global-selector a { font-weight: bold; text-decoration: none; color: #36c; } .hm-global-selector a:hover { text-decoration: underline; } .hm-locate-btn { cursor: pointer; color: #36c; border: 1px solid #a2a9b1; background: #f8f9fa; padding: 2px 6px; font-size: 0.85em; border-radius: 2px; } .hm-locate-btn:hover { background: #fff; border-color: #36c; } `, setup() { if (!config.allowedNamespaces.includes(mw.config.get("wgNamespaceNumber"))) return; const action = mw.config.get("wgAction"); this.addPortlet(action); if (action === 'view' && (config.autoScan || config.autoRun)) { this.silentScan(); } if (mw.util.getParamValue('headmaster') === '1' && ['edit', 'submit'].includes(action)) { const params = new URLSearchParams(window.location.search); params.delete('headmaster'); const searchString = params.toString(); const newUrl = window.location.pathname + (searchString ? '?' + searchString : ''); window.history.replaceState(null, '', newUrl); this.init(); } }, addPortlet(action) { const link = mw.util.addPortletLink(config.menu, '#', 'Headmaster', 'ca-hm', 'Interactively convert pseudo-headings'); if (link) { $(link).on('click', (e) => { e.preventDefault(); if (action === 'view') { this.redirectToEdit(); } else if (['edit', 'submit'].includes(action)) { this.init(); } }); } }, redirectToEdit() { window.location.href = mw.util.getUrl(mw.config.get("wgPageName"), { action: 'edit', headmaster: '1' }); }, silentScan() { new mw.Api().get({ action: 'query', prop: 'revisions', titles: mw.config.get('wgPageName'), rvprop: 'content', rvlimit: 1, formatversion: 2 }).done((data) => { const page = data.query.pages[0]; if (!page || !page.revisions) return; const wikitext = page.revisions[0].content; const tasks = this.analyzeText(wikitext); if (tasks.length > 0) { if (config.autoRun) { this.redirectToEdit(); } else { this.notifyDiscovery(tasks.length); } } }).fail((err) => { mw.log.warn("Headmaster: silentScan failed", err); }); }, notifyDiscovery(count) { const $msg = $('<span>').text(`Headmaster: Found ${count} issues. `).append($('<a>').text('Run conversion').css({ fontWeight: 'bold', cursor: 'pointer' }).on('click', () => this.redirectToEdit())); mw.notify($msg, { tag: 'hm-discovery', autoHide: false }); }, init() { if (!this.styleInjected) { mw.loader.addStyleTag(this.css); this.styleInjected = true; } const $textbox = $('#wpTextbox1'); const wikitext = $textbox.textSelection('getContents'); if (!wikitext) { mw.notify("Textbox is empty or not found.", { type: 'error' }); return; } const tasks = this.analyzeText(wikitext); if (tasks.length === 0) { mw.notify("No issues found."); return; } this.showUI(tasks, wikitext); }, analyzeText(wikitext) { const lines = wikitext.split("\n"); const tasks = []; let currentDepth = 1; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const headingMatch = line.match(/^(={2,6})\s*(.+?)\s*\1\s*$/); if (headingMatch) { currentDepth = headingMatch[1].length; const prevLine = i > 0 ? lines[i - 1].trim() : null; const isSmashed = prevLine !== null && prevLine !== ""; const isExtraSpace = i > 1 && lines[i - 1].trim() === "" && lines[i - 2].trim() === ""; if (isSmashed || isExtraSpace) { tasks.push({ index: i, type: 'WHITESPACE', original: lines[i], isSmashed: isSmashed, isExtraSpace: isExtraSpace }); } } else if (line.startsWith(';') && !line.startsWith(';;')) { const nextLine = lines[i + 1] || ""; if (!nextLine.trim().startsWith(':')) { const cleanText = line.substring(1).replace(/\s*:.*$/, "").trim(); const suggestedLevel = "=".repeat(Math.min(currentDepth + 1, 6)); tasks.push({ index: i, type: 'PSEUDO', original: line, cleanText: cleanText, context: nextLine.substring(0, 60).trim() + (nextLine.length > 60 ? "..." : ""), suggestedLevel: suggestedLevel }); } } } return tasks; }, showUI(tasks, originalWikitext) { $('#hm-panel').remove(); $('#editform').hide(); const visibleTasks = tasks.filter(t => t.type === 'PSEUDO'); const pseudoCount = visibleTasks.length; const whitespaceCount = tasks.filter(t => t.type === 'WHITESPACE').length; let statusMsg = ""; const s = (n) => n !== 1 ? 's' : ''; if (pseudoCount > 0) { statusMsg = `Found <strong>${pseudoCount}</strong> possible pseudo-heading${s(pseudoCount)}.`; if (whitespaceCount > 0) { statusMsg += ` Also fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } } else { statusMsg = `Did not find any pseudo-headings, but fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } const tableRows = pseudoCount > 0 ? visibleTasks.map((task, i) => ` <tr data-task-index="${task.index}"> <td><button class="hm-locate-btn" data-hm-i="${tasks.indexOf(task)}" title="Show in editor">Locate</button></td> <td class="hm-context"><code>${mw.html.escape(task.original)}</code></td> <td><label><input type="radio" name="hm-opt-${i}" value="keep"> Keep</label></td> <td><label><input type="radio" name="hm-opt-${i}" value="bold" checked> <b>Bold</b></label></td> <td><label><input type="radio" name="hm-opt-${i}" value="head"> Heading (${task.suggestedLevel.length})</label></td> <td class="hm-context">${mw.html.escape(task.context || "(end of page)")}</td> </tr> `).join("") : `<tr><td colspan="6" style="text-align:center; padding: 2em; color: #54595d; background: #f8f9fa;"> No interactive heading changes detected. Only background spacing normalization will be applied. </td></tr>`; const $panel = $(` <div id="hm-panel"> <h3 style="margin-top:0">Headmaster v${this.VERSION}: Review pseudo-headings</h3> <div class="hm-global-selector" ${pseudoCount === 0 ? 'style="display:none"' : ''}> <strong>Bulk action:</strong> <a href="#" id="hm-all-keep">Set all to Keep</a> | <a href="#" id="hm-all-bold">Set all to Bold</a> | <a href="#" id="hm-all-head">Set all to Heading</a> </div> <table id="hm-table"> <thead> <tr> <th>Find</th> <th>Source</th> <th colspan="3">Action</th> <th>Next line context</th> </tr> </thead> <tbody>${tableRows}</tbody> </table> <div id="hm-controls"> <span>${statusMsg}</span> <div> <button id="hm-cancel" class="mw-ui-button">Cancel</button> <button id="hm-apply" class="hm-btn-apply">Show changes (diff)</button> </div> </div> </div> `); $('#content').prepend($panel); window.scrollTo(0, 0); $('.hm-locate-btn').on('click', function() { $(document).off('mousedown.hmlocate'); const idx = $(this).data('hm-i'); const task = tasks[idx]; const lines = originalWikitext.split('\n'); let charOffset = 0; for (let j = 0; j < task.index; j++) { charOffset += lines[j].length + 1; } $('#hm-panel').hide(); $('#editform').show(); const $textbox = $('#wpTextbox1'); $textbox.textSelection('setSelection', { start: charOffset, end: charOffset + lines[task.index].length }); $textbox.textSelection('scrollToCaretPosition'); const locateNotify = mw.notify("Locating line " + (task.index + 1) + ". Click outside the editor to return.", { tag: 'hm-locate', type: 'success', autoHide: false }); setTimeout(() => { $(document).on('mousedown.hmlocate', function(e) { if (!$(e.target).closest('#editform, .mw-notification-area').length) { $(document).off('mousedown.hmlocate'); Promise.resolve(locateNotify).then(n => n.close()); $('#editform').hide(); $('#hm-panel').show(); } }); }, 200); }); $('#hm-all-keep').on('click', (e) => { e.preventDefault(); $('input[value="keep"]').prop('checked', true); }); $('#hm-all-bold').on('click', (e) => { e.preventDefault(); $('input[value="bold"]').prop('checked', true); }); $('#hm-all-head').on('click', (e) => { e.preventDefault(); $('input[value="head"]').prop('checked', true); }); $('#hm-cancel').on('click', () => { $('#hm-panel').remove(); $('#editform').show(); }); $('#hm-apply').on('click', () => this.applyChanges(tasks, originalWikitext)); }, getProcessedSummary(existingSummary, bolds, heads, spaces) { if (!config.enableSummary) return existingSummary; let mySummary = ""; let isDefault = false; if (config.customSummary) { mySummary = config.customSummary; } else { isDefault = true; if (bolds > 0 || heads > 0) { const detailsArr = []; if (bolds > 0) detailsArr.push(`${bolds} to bold`); if (heads > 0) detailsArr.push(`${heads} to heading`); const detailsStr = detailsArr.length > 0 ? `(${detailsArr.join(', ')}) ` : ""; mySummary = this.DefaultSummary.replace("{details}", detailsStr); if (spaces > 0) { mySummary += " Also handled [[MOS:BLANKLINE]]."; } } else if (spaces > 0) { mySummary = "Handling [[MOS:BLANKLINE]] using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]"; } } if (!mySummary) return existingSummary; const trimmedOld = existingSummary.trim(); if (!trimmedOld) return mySummary; if (/[.!?]$/.test(trimmedOld)) { return trimmedOld + " " + mySummary; } else { const finalSummary = isDefault ? mySummary.charAt(0).toLowerCase() + mySummary.slice(1) : mySummary; return trimmedOld + "; " + finalSummary; } }, applyChanges(tasks, wikitext) { try { let lines = wikitext.split("\n"); let bCount = 0, hCount = 0, wCount = 0; const pseudoTasks = tasks.filter(t => t.type === 'PSEUDO'); for (let i = pseudoTasks.length - 1; i >= 0; i--) { const task = pseudoTasks[i]; const idx = task.index; const action = $('#hm-table tbody tr').filter(`[data-task-index="${idx}"]`).find('input:checked').val(); if (action === 'bold') { let newValue = `'''${task.cleanText}'''`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; bCount++; } else if (action === 'head') { const h = task.suggestedLevel; let newValue = `${h} ${task.cleanText} ${h}`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; hCount++; } } const spaceTasks = tasks.filter(t => t.type === 'WHITESPACE'); for (let i = spaceTasks.length - 1; i >= 0; i--) { const task = spaceTasks[i]; const idx = task.index; if (task.isSmashed) { lines.splice(idx, 0, ""); } else if (task.isExtraSpace) { let backIdx = idx - 1; while (backIdx > 0 && lines[backIdx].trim() === "" && lines[backIdx - 1].trim() === "") { lines.splice(backIdx, 1); backIdx--; } } wCount++; } if (bCount + hCount + wCount === 0) { mw.notify("No changes selected."); $('#hm-panel').remove(); $('#editform').show(); return; } $('#wpTextbox1').textSelection('setContents', lines.join("\n")); const $summary = $('#wpSummary'); $summary.val(this.getProcessedSummary($summary.val(), bCount, hCount, wCount)); $('#hm-panel').remove(); $('#editform').show(); mw.notify(`Headmaster: Fixed ${wCount} spacing issue(s) and converted ${bCount + hCount} heading(s).`); $('html, body').animate({ scrollTop: 0 }, 'fast'); $('#wpDiff').click(); } catch (err) { $('#editform').show(); mw.notify("Headmaster error: " + err.message, { type: 'error' }); } } }; window.Headmaster = Headmaster; mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => { $(() => Headmaster.setup()); }); })(); /* global ComradeLib, mw, $ */ (async function() { await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-lib.js&action=raw&ctype=text/javascript'); async function getTargetPage(api, name, type) { const titles = type === 'surname' ? [`List of people with the English surname ${name}`, `List of people with surname ${name}`, `${name} (surname)`, `${name} (name)`, name] : [`List of people with given name ${name}`, `List of people named ${name}`, `${name} (given name)`, `${name} (name)`, name]; const res = await api.get({ action: 'query', titles: titles.join('|'), formatversion: 2 }); if (!res.query || !res.query.pages) return null; return titles.find(t => { const pg = res.query.pages.find(p => p.title === t); return pg && !pg.missing; }); } async function processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean) { const { givens, surnames } = await fetchWikidataNameLabels(entity); const nameParts = tClean.split(' '); const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const primarySurname = hasFamilyNameHatnote ? nameParts[0] : (getSmartSortKey(tClean, entity).split(',')[0] || nameParts[nameParts.length - 1]); for (const name of givens) { if (name.toLowerCase() === primarySurname.toLowerCase()) continue; const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'given'); if (target) await verifyAndNudge(api, name, 'given name', currentTitle, hasShortDesc, isOrphan, target); } const finalSurnames = surnames.length > 0 ? surnames : (nameParts.length >= 2 ? [nameParts[nameParts.length - 1]] : []); for (const name of finalSurnames) { const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'surname'); if (target) await verifyAndNudge(api, name, 'surname', currentTitle, hasShortDesc, isOrphan, target); } } async function verifyAndNudge(api, name, type, currentTitle, hasShortDesc, isOrphan, foundTitle) { const res = await api.get({ action: 'query', prop: 'revisions', titles: foundTitle, rvprop: 'content', formatversion: 2 }); const page = res.query.pages[0]; if (!page || page.missing || !page.revisions) return; const text = page.revisions[0].content; const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text); const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text); const isNamePageTitle = foundTitle.includes('List of people with'); if (!isNameMatch && !isNameDab && !isNamePageTitle) return; const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]'); const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text); if (alreadyListed) return; const $container = $('<div>').addClass('comrade-container').addClass(isOrphan ? 'comrade-nudge' : 'comrade-info'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text(isOrphan ? 'Comrade nudge:' : 'Informative:')); const $content = $('<div>').addClass('comrade-content'); $content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({ href: mw.util.getUrl(foundTitle), target: '_blank' }).text(foundTitle), "; 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('Consider adding ', $('<code>').text(snippet)); const $copyBtn = $('<button>').text('Copy & insert').on('click', function() { navigator.clipboard.writeText('\n' + snippet).then(() => { $(this).text('Copied!').prop('disabled', true); const editUrl = mw.util.getUrl(foundTitle, { action: 'edit', summary: 'Adding * {{[[Template:Annotated biography link|anbl]]|[[' + currentTitle + ']]}}' }); window.open(editUrl, '_blank'); }); }); $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); ComradeLib.appendDismiss($container); } async function checkNamingPrecision(currentTitle, isHuman) { const disambigMatch = currentTitle.match(/^(.+?)(?:, | \()/); if (isHuman || !disambigMatch) return; const baseTitle = disambigMatch[1].trim(); const api = new mw.Api(); try { const baseRes = await api.get({ action: 'query', titles: baseTitle, redirects: 1, formatversion: 2 }); const page = baseRes.query.pages[0]; const baseExists = !page.missing; let baseRedirectsToSelf = false; if (baseRes.query.redirects) { baseRedirectsToSelf = baseRes.query.redirects.some(r => r.to === currentTitle); } const searchRes = await api.get({ action: 'query', list: 'allpages', apprefix: baseTitle, aplimit: 50, formatversion: 2 }); const competitors = searchRes.query.allpages.filter(p => { const t = p.title; return t !== currentTitle && t !== baseTitle && (t.startsWith(baseTitle + ', ') || t.startsWith(baseTitle + ' (')); }); if (!baseExists || baseRedirectsToSelf) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').addClass('comrade-content'); if (competitors.length === 0) { $header.append($('<strong>').text('Precision:')); const reason = baseRedirectsToSelf ? "already redirects here" : "is free"; $content.append($('<span>').append(`The base name `, $('<code>').text(baseTitle), ` ${reason}. Consider moving this article.`)); const moveUrl = mw.util.getUrl('Special:MovePage/' + currentTitle, { wpNewTitle: baseTitle, wpReason: 'Unnecessary disambiguation; base name is free/redirects here per [[WP:PRECISION]]' }); $content.append($('<button>').text(`Move to ${baseTitle}`).on('click', () => window.open(moveUrl, '_blank'))); } else { $header.append($('<strong>').text('Observation:')); const state = baseRedirectsToSelf ? 'redirect to this page' : 'redlink'; $content.append($('<span>').append(`Base name `, $('<code>').text(baseTitle), ` has ${competitors.length} competitors, but it is currently a ${state}.`)); $content.append($('<span>').text('Consider if a disambiguation page is needed.')); } $container.append($header, $content); ComradeLib.appendDismiss($container); } } catch (e) { console.warn("Comrade: Precision check failed", e); } } async function checkHumanSort(wikitext, entity, currentTitle, tClean, refName) { const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i); const { givens } = await fetchWikidataNameLabels(entity); if (dsMatch) { const currentDS = dsMatch[1].trim(); if (currentDS.includes(',')) { let [lastPart, firstPart] = currentDS.split(',').map(s => s.trim()); if (firstPart.toLowerCase().endsWith(lastPart.toLowerCase())) { let fixedFirst = firstPart.substring(0, firstPart.length - lastPart.length).trim(); let suggestion = `${fixNameCasing(lastPart, refName)}, ${fixNameCasing(fixedFirst, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, "Redundant surname in given name field."); } else if (givens.length > 0 && lastPart.split(' ').length > 1) { const parts = lastPart.split(' '); const misplacedGiven = parts.find(part => givens.some(gn => gn.toLowerCase() === part.toLowerCase())); if (misplacedGiven) { const newSurname = parts.filter(p => p !== misplacedGiven).join(' '); let suggestion = `${fixNameCasing(newSurname, refName)}, ${fixNameCasing(misplacedGiven + ' ' + firstPart, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, `Wikidata suggests "${misplacedGiven}" is a given name.`); } } } } else { const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const nameParts = tClean.split(' '); const targetSortName = hasFamilyNameHatnote ? `${nameParts[0]}, ${nameParts.slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { const $dsMissing = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Add: {{DEFAULTSORT:${targetSortName}}}`).on('click', () => performSortCorrection(targetSortName, "add")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Tag is missing. `, $btn)); $dsMissing.append($header); ComradeLib.appendDismiss($dsMissing); } } } async function fetchWikidataEntity(qid) { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: qid, props: 'claims|labels', languages: 'en', format: 'json', origin: '*' }); try { const wikiData = await fetch(url).then(r => r.json()); return wikiData.entities[qid]; } catch (e) { return null; } } function getSmartSortKey(fullName, entity) { const countryIds = entity.claims?.P27?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const locationIds = entity.claims?.P17?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const placeIds = [...(entity.claims?.P19 || []), ...(entity.claims?.P119 || [])].map(c => c.mainsnak?.datavalue?.value?.id).filter(Boolean); const allRelevantIds = [...countryIds, ...locationIds, ...placeIds]; const birthYear = parseInt(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.match(/[+-](\d{4})/)?.[1]) || null; if (fullName.startsWith("Saint ")) return fullName.replace("Saint ", ""); const arabicParticles = /\b(Abu|Abd|Abdel|Abdul|ben|bin|bint)\b/i; if (fullName.match(arabicParticles)) { if (birthYear && birthYear > 1900) { const parts = fullName.split(' '); const binIndex = parts.findIndex(p => p.toLowerCase() === 'bin' || p.toLowerCase() === 'ben'); if (binIndex > 0) return parts.slice(binIndex).join(' ') + ', ' + parts.slice(0, binIndex).join(' '); } else { return fullName; } } const eastAsianQids = ["Q148", "Q865", "Q884", "Q424", "Q17", "Q881"]; if (allRelevantIds.some(id => eastAsianQids.includes(id))) { if (allRelevantIds.includes("Q17") && birthYear && birthYear > 1885) return westernSort(fullName); const parts = fullName.split(' '); if (parts.length > 1) return `${parts[0]}, ${parts.slice(1).join(' ')}`; } return westernSort(fullName); } function westernSort(name) { let cleanName = name; if (name.startsWith("O'")) cleanName = name.replace("O'", "O"); const parts = cleanName.split(' '); if (parts.length < 2) return cleanName; const surname = parts.pop(); if (["Jr.", "Jr", "III", "II", "Sr.", "Sr"].includes(surname)) { const actualSurname = parts.pop(); return `${actualSurname}, ${parts.join(' ')} ${surname}`; } return `${surname}, ${parts.join(' ')}`; } async function performSortCorrection(newName, type) { const api = new mw.Api(); const title = mw.config.get("wgPageName"); try { const data = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = data.query.pages[0].revisions[0].content; const dsRegex = /\{\{\s*(?:DEFAULTSORT|Defaultsort)\s*:[^}]+\}\}/i; const newTag = `{{DEFAULTSORT:${newName}}}`; if (dsRegex.test(content)) { content = content.replace(dsRegex, newTag); } else { const catRegex = /\[\[Category:/i; content = catRegex.test(content) ? content.replace(catRegex, `${newTag}\n[[Category:`) : content.trim() + `\n\n${newTag}`; } await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Setting DEFAULTSORT to ${newName}`, nocreate: true }); mw.notify(`DEFAULTSORT ${type === "add" ? "added" : "corrected"} to ${newName}!`, { title: 'Comrade Success', type: 'success' }); setTimeout(() => location.reload(), 700); } catch (err) { mw.notify('Failed to save DEFAULTSORT.', { type: 'error' }); } } function stripDiacritics(str) { return str.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); } function renderSortNudge(target, reason) { const $dsNudge = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Correct to: ${target}`).on('click', () => performSortCorrection(target, "format")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` ${reason} `, $btn)); $dsNudge.append($header); ComradeLib.appendDismiss($dsNudge); } async function fetchWikidataNameLabels(entity) { const familyIds = entity.claims?.P734?.map(c => c.mainsnak.datavalue.value.id) || []; const givenIds = entity.claims?.P735?.map(c => c.mainsnak.datavalue.value.id) || []; const allNameIds = [...new Set([...familyIds, ...givenIds])]; if (allNameIds.length === 0) return { givens: [], surnames: [] }; const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: allNameIds.join('|'), props: 'labels', languages: 'en', format: 'json', origin: '*' }); const nameData = await fetch(url).then(r => r.json()); return { givens: givenIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean), surnames: familyIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean) }; } function fixNameCasing(part, referenceName) { return part.split(' ').map(word => { const match = referenceName.match(new RegExp(`\\b${word}\\b`, 'i')); if (match) return match[0]; return referenceName.split(' ').find(tWord => word.toLowerCase().startsWith(tWord.toLowerCase()) || tWord.toLowerCase().startsWith(word.toLowerCase())) || word; }).join(' '); } async function init() { const api = new mw.Api(); const ns = mw.config.get("wgNamespaceNumber"); const action = mw.config.get("wgAction"); if (mw.config.get("wgDBname") !== "enwiki" || action !== "view") return; if (ns === 14) { const $btn = $('<button>').attr('id', 'comrade-audit-btn').text('Audit category quality').css({ 'margin-bottom': '10px', 'padding': '5px 10px', 'cursor': 'pointer' }).on('click', ComradeLib.performCategoryAudit); $('.mw-category-generated').prepend($btn); return; } if (ns !== 0) return; const qid = mw.config.get("wgWikibaseItemId"); const currentTitle = mw.config.get("wgTitle"); 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; // Diacritic and domain checks const cleanTitle = stripDiacritics(currentTitle); if (cleanTitle !== currentTitle) await ComradeLib.checkAndRenderRedirect(cleanTitle, currentTitle, "R to diacritic", "Diacritic"); await ComradeLib.checkDomainRedirect(qid, currentTitle); // Orphan logic 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 $btn = $('<button>').text('Remove orphan tag').on('click', async () => await ComradeLib.performDeorphan()); const $statusText = $('<span>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn); $container.append($('<div>').addClass('comrade-header').append($statusText)); ComradeLib.appendDismiss($container); } // Wikidata/human logic let entity = qid ? await fetchWikidataEntity(qid) : null; let isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5"); await checkNamingPrecision(currentTitle, isHuman); if (isHuman && entity) { const tClean = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim(); const refName = entity.labels?.en?.value || currentTitle; const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext); const isOrphan = isOrphanTagged && backLinkCount < 1; await ComradeLib.checkShortDescDates(wikitext, entity); // Check DEFAULTSORT await checkHumanSort(wikitext, entity, currentTitle, tClean, refName); const longName = ComradeLib.getLongNameFromWikitext(wikitext); if (longName && longName.length > tClean.length) { let sortKey = hasFamilyNameHatnote ? `${longName.split(' ')[0]}, ${longName.split(' ').slice(1).join(' ')}` : getSmartSortKey(longName, entity); const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`; await ComradeLib.checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext); } // Sort name R const targetSortName = hasFamilyNameHatnote ? `${tClean.split(' ')[0]}, ${tClean.split(' ').slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName && targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { let targetPage = currentTitle.includes(' (') ? currentTitle.split(' (')[0] : currentTitle; let rCat = targetPage !== currentTitle ? "R from ambiguous sort name" : "R from sort name"; if (targetSortName.includes(',')) { const p = targetSortName.split(','); rCat += `|${p[0].trim().charAt(0).toUpperCase()}|${p[1].trim().charAt(0).toUpperCase()}`; } await ComradeLib.checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name"); } // Name lists await processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean); } // Quality/ORES const oresWait = setInterval(async () => { if ($('.article_quality').length) { clearInterval(oresWait); await ComradeLib.checkQualityConsistency(); } }, 500); setTimeout(() => clearInterval(oresWait), 5000); } catch (err) { console.error("Comrade error:", err); } } init(); })(); //</nowiki> 1k9smr1x1poa82s0rnisyxmbkm0jssp 739825 739821 2026-04-30T05:53:36Z Sam Sailor 26820 Test 739825 javascript text/javascript //<nowiki> (function() { if (window.Headmaster) return; const VERSION = "0.9.2.1"; const defaultNS = [0, 118]; const userNS = window.headmaster_ns || []; const config = { menu: window.headmaster_menu || 'p-cactions', enableSummary: window.headmaster_do_summary !== false, customSummary: window.headmaster_summary || "", allowedNamespaces: Array.from(new Set([...defaultNS, ...userNS])), autoScan: window.headmaster_auto_scan !== false, autoRun: window.headmaster_auto_run === false }; const Headmaster = { VERSION: VERSION, DefaultSummary: "Converting [[MOS:PSEUDOHEAD|pseudo-headings]] {details}using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]. Semicolon markup is reserved for [[MOS:DLIST|description lists]].", styleInjected: false, css: ` #hm-panel { clear: both; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; margin-bottom: 1.5em; border-radius: 2px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } #hm-table { width: 100%; border-collapse: collapse; background: white; margin-top: 10px; } #hm-table th { background: #f2f2f2; border: 1px solid #a2a9b1; padding: 8px; text-align: left; } #hm-table td { border: 1px solid #a2a9b1; padding: 8px; vertical-align: middle; } .hm-context { font-family: 'Courier New', monospace; font-size: 0.9em; color: #54595d; background: #fdfdfd; } #hm-controls { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; } #hm-controls button { margin-left: 10px; padding: 6px 16px; cursor: pointer; border-radius: 2px; } .hm-btn-apply { background: #36c; color: white; border: 1px solid #36c; font-weight: bold; } .hm-btn-apply:hover { background: #447ff5; } .hm-global-selector { font-size: 0.9em; color: #202122; background: #eaecf0; padding: 8px; border-radius: 2px; border: 1px solid #a2a9b1; } .hm-global-selector a { font-weight: bold; text-decoration: none; color: #36c; } .hm-global-selector a:hover { text-decoration: underline; } .hm-locate-btn { cursor: pointer; color: #36c; border: 1px solid #a2a9b1; background: #f8f9fa; padding: 2px 6px; font-size: 0.85em; border-radius: 2px; } .hm-locate-btn:hover { background: #fff; border-color: #36c; } `, setup() { if (!config.allowedNamespaces.includes(mw.config.get("wgNamespaceNumber"))) return; const action = mw.config.get("wgAction"); this.addPortlet(action); if (action === 'view' && (config.autoScan || config.autoRun)) { this.silentScan(); } if (mw.util.getParamValue('headmaster') === '1' && ['edit', 'submit'].includes(action)) { const params = new URLSearchParams(window.location.search); params.delete('headmaster'); const searchString = params.toString(); const newUrl = window.location.pathname + (searchString ? '?' + searchString : ''); window.history.replaceState(null, '', newUrl); this.init(); } }, addPortlet(action) { const link = mw.util.addPortletLink(config.menu, '#', 'Headmaster', 'ca-hm', 'Interactively convert pseudo-headings'); if (link) { $(link).on('click', (e) => { e.preventDefault(); if (action === 'view') { this.redirectToEdit(); } else if (['edit', 'submit'].includes(action)) { this.init(); } }); } }, redirectToEdit() { window.location.href = mw.util.getUrl(mw.config.get("wgPageName"), { action: 'edit', headmaster: '1' }); }, silentScan() { new mw.Api().get({ action: 'query', prop: 'revisions', titles: mw.config.get('wgPageName'), rvprop: 'content', rvlimit: 1, formatversion: 2 }).done((data) => { const page = data.query.pages[0]; if (!page || !page.revisions) return; const wikitext = page.revisions[0].content; const tasks = this.analyzeText(wikitext); if (tasks.length > 0) { if (config.autoRun) { this.redirectToEdit(); } else { this.notifyDiscovery(tasks.length); } } }).fail((err) => { mw.log.warn("Headmaster: silentScan failed", err); }); }, notifyDiscovery(count) { const $msg = $('<span>').text(`Headmaster: Found ${count} issues. `).append($('<a>').text('Run conversion').css({ fontWeight: 'bold', cursor: 'pointer' }).on('click', () => this.redirectToEdit())); mw.notify($msg, { tag: 'hm-discovery', autoHide: false }); }, init() { if (!this.styleInjected) { mw.loader.addStyleTag(this.css); this.styleInjected = true; } const $textbox = $('#wpTextbox1'); const wikitext = $textbox.textSelection('getContents'); if (!wikitext) { mw.notify("Textbox is empty or not found.", { type: 'error' }); return; } const tasks = this.analyzeText(wikitext); if (tasks.length === 0) { mw.notify("No issues found."); return; } this.showUI(tasks, wikitext); }, analyzeText(wikitext) { const lines = wikitext.split("\n"); const tasks = []; let currentDepth = 1; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const headingMatch = line.match(/^(={2,6})\s*(.+?)\s*\1\s*$/); if (headingMatch) { currentDepth = headingMatch[1].length; const prevLine = i > 0 ? lines[i - 1].trim() : null; const isSmashed = prevLine !== null && prevLine !== ""; const isExtraSpace = i > 1 && lines[i - 1].trim() === "" && lines[i - 2].trim() === ""; if (isSmashed || isExtraSpace) { tasks.push({ index: i, type: 'WHITESPACE', original: lines[i], isSmashed: isSmashed, isExtraSpace: isExtraSpace }); } } else if (line.startsWith(';') && !line.startsWith(';;')) { const nextLine = lines[i + 1] || ""; if (!nextLine.trim().startsWith(':')) { const cleanText = line.substring(1).replace(/\s*:.*$/, "").trim(); const suggestedLevel = "=".repeat(Math.min(currentDepth + 1, 6)); tasks.push({ index: i, type: 'PSEUDO', original: line, cleanText: cleanText, context: nextLine.substring(0, 60).trim() + (nextLine.length > 60 ? "..." : ""), suggestedLevel: suggestedLevel }); } } } return tasks; }, showUI(tasks, originalWikitext) { $('#hm-panel').remove(); $('#editform').hide(); const visibleTasks = tasks.filter(t => t.type === 'PSEUDO'); const pseudoCount = visibleTasks.length; const whitespaceCount = tasks.filter(t => t.type === 'WHITESPACE').length; let statusMsg = ""; const s = (n) => n !== 1 ? 's' : ''; if (pseudoCount > 0) { statusMsg = `Found <strong>${pseudoCount}</strong> possible pseudo-heading${s(pseudoCount)}.`; if (whitespaceCount > 0) { statusMsg += ` Also fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } } else { statusMsg = `Did not find any pseudo-headings, but fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } const tableRows = pseudoCount > 0 ? visibleTasks.map((task, i) => ` <tr data-task-index="${task.index}"> <td><button class="hm-locate-btn" data-hm-i="${tasks.indexOf(task)}" title="Show in editor">Locate</button></td> <td class="hm-context"><code>${mw.html.escape(task.original)}</code></td> <td><label><input type="radio" name="hm-opt-${i}" value="keep"> Keep</label></td> <td><label><input type="radio" name="hm-opt-${i}" value="bold" checked> <b>Bold</b></label></td> <td><label><input type="radio" name="hm-opt-${i}" value="head"> Heading (${task.suggestedLevel.length})</label></td> <td class="hm-context">${mw.html.escape(task.context || "(end of page)")}</td> </tr> `).join("") : `<tr><td colspan="6" style="text-align:center; padding: 2em; color: #54595d; background: #f8f9fa;"> No interactive heading changes detected. Only background spacing normalization will be applied. </td></tr>`; const $panel = $(` <div id="hm-panel"> <h3 style="margin-top:0">Headmaster v${this.VERSION}: Review pseudo-headings</h3> <div class="hm-global-selector" ${pseudoCount === 0 ? 'style="display:none"' : ''}> <strong>Bulk action:</strong> <a href="#" id="hm-all-keep">Set all to Keep</a> | <a href="#" id="hm-all-bold">Set all to Bold</a> | <a href="#" id="hm-all-head">Set all to Heading</a> </div> <table id="hm-table"> <thead> <tr> <th>Find</th> <th>Source</th> <th colspan="3">Action</th> <th>Next line context</th> </tr> </thead> <tbody>${tableRows}</tbody> </table> <div id="hm-controls"> <span>${statusMsg}</span> <div> <button id="hm-cancel" class="mw-ui-button">Cancel</button> <button id="hm-apply" class="hm-btn-apply">Show changes (diff)</button> </div> </div> </div> `); $('#content').prepend($panel); window.scrollTo(0, 0); $('.hm-locate-btn').on('click', function() { $(document).off('mousedown.hmlocate'); const idx = $(this).data('hm-i'); const task = tasks[idx]; const lines = originalWikitext.split('\n'); let charOffset = 0; for (let j = 0; j < task.index; j++) { charOffset += lines[j].length + 1; } $('#hm-panel').hide(); $('#editform').show(); const $textbox = $('#wpTextbox1'); $textbox.textSelection('setSelection', { start: charOffset, end: charOffset + lines[task.index].length }); $textbox.textSelection('scrollToCaretPosition'); const locateNotify = mw.notify("Locating line " + (task.index + 1) + ". Click outside the editor to return.", { tag: 'hm-locate', type: 'success', autoHide: false }); setTimeout(() => { $(document).on('mousedown.hmlocate', function(e) { if (!$(e.target).closest('#editform, .mw-notification-area').length) { $(document).off('mousedown.hmlocate'); Promise.resolve(locateNotify).then(n => n.close()); $('#editform').hide(); $('#hm-panel').show(); } }); }, 200); }); $('#hm-all-keep').on('click', (e) => { e.preventDefault(); $('input[value="keep"]').prop('checked', true); }); $('#hm-all-bold').on('click', (e) => { e.preventDefault(); $('input[value="bold"]').prop('checked', true); }); $('#hm-all-head').on('click', (e) => { e.preventDefault(); $('input[value="head"]').prop('checked', true); }); $('#hm-cancel').on('click', () => { $('#hm-panel').remove(); $('#editform').show(); }); $('#hm-apply').on('click', () => this.applyChanges(tasks, originalWikitext)); }, getProcessedSummary(existingSummary, bolds, heads, spaces) { if (!config.enableSummary) return existingSummary; let mySummary = ""; let isDefault = false; if (config.customSummary) { mySummary = config.customSummary; } else { isDefault = true; if (bolds > 0 || heads > 0) { const detailsArr = []; if (bolds > 0) detailsArr.push(`${bolds} to bold`); if (heads > 0) detailsArr.push(`${heads} to heading`); const detailsStr = detailsArr.length > 0 ? `(${detailsArr.join(', ')}) ` : ""; mySummary = this.DefaultSummary.replace("{details}", detailsStr); if (spaces > 0) { mySummary += " Also handled [[MOS:BLANKLINE]]."; } } else if (spaces > 0) { mySummary = "Handling [[MOS:BLANKLINE]] using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]"; } } if (!mySummary) return existingSummary; const trimmedOld = existingSummary.trim(); if (!trimmedOld) return mySummary; if (/[.!?]$/.test(trimmedOld)) { return trimmedOld + " " + mySummary; } else { const finalSummary = isDefault ? mySummary.charAt(0).toLowerCase() + mySummary.slice(1) : mySummary; return trimmedOld + "; " + finalSummary; } }, applyChanges(tasks, wikitext) { try { let lines = wikitext.split("\n"); let bCount = 0, hCount = 0, wCount = 0; const pseudoTasks = tasks.filter(t => t.type === 'PSEUDO'); for (let i = pseudoTasks.length - 1; i >= 0; i--) { const task = pseudoTasks[i]; const idx = task.index; const action = $('#hm-table tbody tr').filter(`[data-task-index="${idx}"]`).find('input:checked').val(); if (action === 'bold') { let newValue = `'''${task.cleanText}'''`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; bCount++; } else if (action === 'head') { const h = task.suggestedLevel; let newValue = `${h} ${task.cleanText} ${h}`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; hCount++; } } const spaceTasks = tasks.filter(t => t.type === 'WHITESPACE'); for (let i = spaceTasks.length - 1; i >= 0; i--) { const task = spaceTasks[i]; const idx = task.index; if (task.isSmashed) { lines.splice(idx, 0, ""); } else if (task.isExtraSpace) { let backIdx = idx - 1; while (backIdx > 0 && lines[backIdx].trim() === "" && lines[backIdx - 1].trim() === "") { lines.splice(backIdx, 1); backIdx--; } } wCount++; } if (bCount + hCount + wCount === 0) { mw.notify("No changes selected."); $('#hm-panel').remove(); $('#editform').show(); return; } $('#wpTextbox1').textSelection('setContents', lines.join("\n")); const $summary = $('#wpSummary'); $summary.val(this.getProcessedSummary($summary.val(), bCount, hCount, wCount)); $('#hm-panel').remove(); $('#editform').show(); mw.notify(`Headmaster: Fixed ${wCount} spacing issue(s) and converted ${bCount + hCount} heading(s).`); $('html, body').animate({ scrollTop: 0 }, 'fast'); $('#wpDiff').click(); } catch (err) { $('#editform').show(); mw.notify("Headmaster error: " + err.message, { type: 'error' }); } } }; window.Headmaster = Headmaster; mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => { $(() => Headmaster.setup()); }); })(); /* global ComradeLib, mw, $ */ (async function() { await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-lib.js&action=raw&ctype=text/javascript'); async function getTargetPage(api, name, type) { const titles = type === 'surname' ? [`List of people with the English surname ${name}`, `List of people with surname ${name}`, `${name} (surname)`, `${name} (name)`, name] : [`List of people with given name ${name}`, `List of people named ${name}`, `${name} (given name)`, `${name} (name)`, name]; const res = await api.get({ action: 'query', titles: titles.join('|'), formatversion: 2 }); if (!res.query || !res.query.pages) return null; return titles.find(t => { const pg = res.query.pages.find(p => p.title === t); return pg && !pg.missing; }); } async function processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean) { const { givens, surnames } = await fetchWikidataNameLabels(entity); const nameParts = tClean.split(' '); const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const primarySurname = hasFamilyNameHatnote ? nameParts[0] : (getSmartSortKey(tClean, entity).split(',')[0] || nameParts[nameParts.length - 1]); for (const name of givens) { if (name.toLowerCase() === primarySurname.toLowerCase()) continue; const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'given'); if (target) await verifyAndNudge(api, name, 'given name', currentTitle, hasShortDesc, isOrphan, target); } const finalSurnames = surnames.length > 0 ? surnames : (nameParts.length >= 2 ? [nameParts[nameParts.length - 1]] : []); for (const name of finalSurnames) { const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'surname'); if (target) await verifyAndNudge(api, name, 'surname', currentTitle, hasShortDesc, isOrphan, target); } } async function verifyAndNudge(api, name, type, currentTitle, hasShortDesc, isOrphan, foundTitle) { const res = await api.get({ action: 'query', prop: 'revisions', titles: foundTitle, rvprop: 'content', formatversion: 2 }); const page = res.query.pages[0]; if (!page || page.missing || !page.revisions) return; const text = page.revisions[0].content; const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text); const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text); const isNamePageTitle = foundTitle.includes('List of people with'); if (!isNameMatch && !isNameDab && !isNamePageTitle) return; const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]'); const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text); if (alreadyListed) return; const $container = $('<div>').addClass('comrade-container').addClass(isOrphan ? 'comrade-nudge' : 'comrade-info'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text(isOrphan ? 'Comrade nudge:' : 'Informative:')); const $content = $('<div>').addClass('comrade-content'); $content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({ href: mw.util.getUrl(foundTitle), target: '_blank' }).text(foundTitle), "; 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('Consider adding ', $('<code>').text(snippet)); const $copyBtn = $('<button>').text('Copy & insert').on('click', function() { navigator.clipboard.writeText('\n' + snippet).then(() => { $(this).text('Copied!').prop('disabled', true); const editUrl = mw.util.getUrl(foundTitle, { action: 'edit', summary: 'Adding * {{[[Template:Annotated biography link|anbl]]|[[' + currentTitle + ']]}}' }); window.open(editUrl, '_blank'); }); }); $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); ComradeLib.appendDismiss($container); } async function checkNamingPrecision(currentTitle, isHuman) { const disambigMatch = currentTitle.match(/^(.+?)(?:, | \()/); if (isHuman || !disambigMatch) return; const baseTitle = disambigMatch[1].trim(); const api = new mw.Api(); try { const baseRes = await api.get({ action: 'query', titles: baseTitle, redirects: 1, formatversion: 2 }); const page = baseRes.query.pages[0]; const baseExists = !page.missing; let baseRedirectsToSelf = false; if (baseRes.query.redirects) { baseRedirectsToSelf = baseRes.query.redirects.some(r => r.to === currentTitle); } const searchRes = await api.get({ action: 'query', list: 'allpages', apprefix: baseTitle, aplimit: 50, formatversion: 2 }); const competitors = searchRes.query.allpages.filter(p => { const t = p.title; return t !== currentTitle && t !== baseTitle && (t.startsWith(baseTitle + ', ') || t.startsWith(baseTitle + ' (')); }); if (!baseExists || baseRedirectsToSelf) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').addClass('comrade-content'); if (competitors.length === 0) { $header.append($('<strong>').text('Precision:')); const reason = baseRedirectsToSelf ? "already redirects here" : "is free"; $content.append($('<span>').append(`The base name `, $('<code>').text(baseTitle), ` ${reason}. Consider moving this article.`)); const moveUrl = mw.util.getUrl('Special:MovePage/' + currentTitle, { wpNewTitle: baseTitle, wpReason: 'Unnecessary disambiguation; base name is free/redirects here per [[WP:PRECISION]]' }); $content.append($('<button>').text(`Move to ${baseTitle}`).on('click', () => window.open(moveUrl, '_blank'))); } else { $header.append($('<strong>').text('Observation:')); const state = baseRedirectsToSelf ? 'redirect to this page' : 'redlink'; $content.append($('<span>').append(`Base name `, $('<code>').text(baseTitle), ` has ${competitors.length} competitors, but it is currently a ${state}.`)); $content.append($('<span>').text('Consider if a disambiguation page is needed.')); } $container.append($header, $content); ComradeLib.appendDismiss($container); } } catch (e) { console.warn("Comrade: Precision check failed", e); } } async function checkHumanSort(wikitext, entity, currentTitle, tClean, refName) { const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i); const { givens } = await fetchWikidataNameLabels(entity); if (dsMatch) { const currentDS = dsMatch[1].trim(); if (currentDS.includes(',')) { let [lastPart, firstPart] = currentDS.split(',').map(s => s.trim()); if (firstPart.toLowerCase().endsWith(lastPart.toLowerCase())) { let fixedFirst = firstPart.substring(0, firstPart.length - lastPart.length).trim(); let suggestion = `${fixNameCasing(lastPart, refName)}, ${fixNameCasing(fixedFirst, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, "Redundant surname in given name field."); } else if (givens.length > 0 && lastPart.split(' ').length > 1) { const parts = lastPart.split(' '); const misplacedGiven = parts.find(part => givens.some(gn => gn.toLowerCase() === part.toLowerCase())); if (misplacedGiven) { const newSurname = parts.filter(p => p !== misplacedGiven).join(' '); let suggestion = `${fixNameCasing(newSurname, refName)}, ${fixNameCasing(misplacedGiven + ' ' + firstPart, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, `Wikidata suggests "${misplacedGiven}" is a given name.`); } } } } else { const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const nameParts = tClean.split(' '); const targetSortName = hasFamilyNameHatnote ? `${nameParts[0]}, ${nameParts.slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { const $dsMissing = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Add: {{DEFAULTSORT:${targetSortName}}}`).on('click', () => performSortCorrection(targetSortName, "add")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Tag is missing. `, $btn)); $dsMissing.append($header); ComradeLib.appendDismiss($dsMissing); } } } async function fetchWikidataEntity(qid) { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: qid, props: 'claims|labels', languages: 'en', format: 'json', origin: '*' }); try { const wikiData = await fetch(url).then(r => r.json()); return wikiData.entities[qid]; } catch (e) { return null; } } function getSmartSortKey(fullName, entity) { const countryIds = entity.claims?.P27?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const locationIds = entity.claims?.P17?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const placeIds = [...(entity.claims?.P19 || []), ...(entity.claims?.P119 || [])].map(c => c.mainsnak?.datavalue?.value?.id).filter(Boolean); const allRelevantIds = [...countryIds, ...locationIds, ...placeIds]; const birthYear = parseInt(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.match(/[+-](\d{4})/)?.[1]) || null; if (fullName.startsWith("Saint ")) return fullName.replace("Saint ", ""); const arabicParticles = /\b(Abu|Abd|Abdel|Abdul|ben|bin|bint)\b/i; if (fullName.match(arabicParticles)) { if (birthYear && birthYear > 1900) { const parts = fullName.split(' '); const binIndex = parts.findIndex(p => p.toLowerCase() === 'bin' || p.toLowerCase() === 'ben'); if (binIndex > 0) return parts.slice(binIndex).join(' ') + ', ' + parts.slice(0, binIndex).join(' '); } else { return fullName; } } const eastAsianQids = ["Q148", "Q865", "Q884", "Q424", "Q17", "Q881"]; if (allRelevantIds.some(id => eastAsianQids.includes(id))) { if (allRelevantIds.includes("Q17") && birthYear && birthYear > 1885) return westernSort(fullName); const parts = fullName.split(' '); if (parts.length > 1) return `${parts[0]}, ${parts.slice(1).join(' ')}`; } return westernSort(fullName); } function westernSort(name) { let cleanName = name; if (name.startsWith("O'")) cleanName = name.replace("O'", "O"); const parts = cleanName.split(' '); if (parts.length < 2) return cleanName; const surname = parts.pop(); if (["Jr.", "Jr", "III", "II", "Sr.", "Sr"].includes(surname)) { const actualSurname = parts.pop(); return `${actualSurname}, ${parts.join(' ')} ${surname}`; } return `${surname}, ${parts.join(' ')}`; } async function performSortCorrection(newName, type) { const api = new mw.Api(); const title = mw.config.get("wgPageName"); try { const data = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = data.query.pages[0].revisions[0].content; const dsRegex = /\{\{\s*(?:DEFAULTSORT|Defaultsort)\s*:[^}]+\}\}/i; const newTag = `{{DEFAULTSORT:${newName}}}`; if (dsRegex.test(content)) { content = content.replace(dsRegex, newTag); } else { const catRegex = /\[\[Category:/i; content = catRegex.test(content) ? content.replace(catRegex, `${newTag}\n[[Category:`) : content.trim() + `\n\n${newTag}`; } await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Setting DEFAULTSORT to ${newName}`, nocreate: true }); mw.notify(`DEFAULTSORT ${type === "add" ? "added" : "corrected"} to ${newName}!`, { title: 'Comrade Success', type: 'success' }); setTimeout(() => location.reload(), 700); } catch (err) { mw.notify('Failed to save DEFAULTSORT.', { type: 'error' }); } } function renderSortNudge(target, reason) { const $dsNudge = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Correct to: ${target}`).on('click', () => performSortCorrection(target, "format")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` ${reason} `, $btn)); $dsNudge.append($header); ComradeLib.appendDismiss($dsNudge); } async function fetchWikidataNameLabels(entity) { const familyIds = entity.claims?.P734?.map(c => c.mainsnak.datavalue.value.id) || []; const givenIds = entity.claims?.P735?.map(c => c.mainsnak.datavalue.value.id) || []; const allNameIds = [...new Set([...familyIds, ...givenIds])]; if (allNameIds.length === 0) return { givens: [], surnames: [] }; const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: allNameIds.join('|'), props: 'labels', languages: 'en', format: 'json', origin: '*' }); const nameData = await fetch(url).then(r => r.json()); return { givens: givenIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean), surnames: familyIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean) }; } function fixNameCasing(part, referenceName) { return part.split(' ').map(word => { const match = referenceName.match(new RegExp(`\\b${word}\\b`, 'i')); if (match) return match[0]; return referenceName.split(' ').find(tWord => word.toLowerCase().startsWith(tWord.toLowerCase()) || tWord.toLowerCase().startsWith(word.toLowerCase())) || word; }).join(' '); } async function init() { const api = new mw.Api(); const ns = mw.config.get("wgNamespaceNumber"); const action = mw.config.get("wgAction"); if (mw.config.get("wgDBname") !== "enwiki" || action !== "view") return; if (ns === 14) { const $btn = $('<button>').attr('id', 'comrade-audit-btn').text('Audit category quality').css({ 'margin-bottom': '10px', 'padding': '5px 10px', 'cursor': 'pointer' }).on('click', ComradeLib.performCategoryAudit); $('.mw-category-generated').prepend($btn); return; } if (ns !== 0) return; const qid = mw.config.get("wgWikibaseItemId"); const currentTitle = mw.config.get("wgTitle"); 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; // Ligature/ASCII-only/diacritic and domain const norms = ComradeLib.getNormalizedTitles(currentTitle); if (norms.ligature) { await ComradeLib.checkAndRenderRedirect(norms.ligature, currentTitle, "R to ligature", "Ligature"); } if (norms.asciiOnly && norms.asciiOnly !== norms.ligature) { await ComradeLib.checkAndRenderRedirect(norms.asciiOnly, currentTitle, "R from ASCII-only", "ASCII-only"); } if (norms.diacritic && norms.diacritic !== norms.ligature && norms.diacritic !== norms.asciiOnly) { await ComradeLib.checkAndRenderRedirect(norms.diacritic, currentTitle, "R to diacritic", "Diacritic"); } await ComradeLib.checkDomainRedirect(qid, currentTitle); // Orphan logic 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 $btn = $('<button>').text('Remove orphan tag').on('click', async () => await ComradeLib.performDeorphan()); const $statusText = $('<span>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn); $container.append($('<div>').addClass('comrade-header').append($statusText)); ComradeLib.appendDismiss($container); } // Wikidata/human logic let entity = qid ? await fetchWikidataEntity(qid) : null; let isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5"); await checkNamingPrecision(currentTitle, isHuman); if (isHuman && entity) { const tClean = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim(); const refName = entity.labels?.en?.value || currentTitle; const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext); const isOrphan = isOrphanTagged && backLinkCount < 1; await ComradeLib.checkShortDescDates(wikitext, entity); // Check DEFAULTSORT await checkHumanSort(wikitext, entity, currentTitle, tClean, refName); const longName = ComradeLib.getLongNameFromWikitext(wikitext); if (longName && longName.length > tClean.length) { let sortKey = hasFamilyNameHatnote ? `${longName.split(' ')[0]}, ${longName.split(' ').slice(1).join(' ')}` : getSmartSortKey(longName, entity); const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`; await ComradeLib.checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext); } // Sort name R const targetSortName = hasFamilyNameHatnote ? `${tClean.split(' ')[0]}, ${tClean.split(' ').slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName && targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { let targetPage = currentTitle.includes(' (') ? currentTitle.split(' (')[0] : currentTitle; let rCat = targetPage !== currentTitle ? "R from ambiguous sort name" : "R from sort name"; if (targetSortName.includes(',')) { const p = targetSortName.split(','); rCat += `|${p[0].trim().charAt(0).toUpperCase()}|${p[1].trim().charAt(0).toUpperCase()}`; } await ComradeLib.checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name"); } // Name lists await processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean); } // Quality/ORES const oresWait = setInterval(async () => { if ($('.article_quality').length) { clearInterval(oresWait); await ComradeLib.checkQualityConsistency(); } }, 500); setTimeout(() => clearInterval(oresWait), 5000); } catch (err) { console.error("Comrade error:", err); } } init(); })(); //</nowiki> huimrd1zxn1c6qcgey7v3zivz1ux86f 739829 739825 2026-04-30T06:11:45Z Sam Sailor 26820 Test 739829 javascript text/javascript //<nowiki> /* global ComradeLib, mw, $ */ (async function() { await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-lib.js&action=raw&ctype=text/javascript'); async function getTargetPage(api, name, type) { const titles = type === 'surname' ? [`List of people with the English surname ${name}`, `List of people with surname ${name}`, `${name} (surname)`, `${name} (name)`, name] : [`List of people with given name ${name}`, `List of people named ${name}`, `${name} (given name)`, `${name} (name)`, name]; const res = await api.get({ action: 'query', titles: titles.join('|'), formatversion: 2 }); if (!res.query || !res.query.pages) return null; return titles.find(t => { const pg = res.query.pages.find(p => p.title === t); return pg && !pg.missing; }); } async function processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean) { const { givens, surnames } = await fetchWikidataNameLabels(entity); const nameParts = tClean.split(' '); const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const primarySurname = hasFamilyNameHatnote ? nameParts[0] : (getSmartSortKey(tClean, entity).split(',')[0] || nameParts[nameParts.length - 1]); for (const name of givens) { if (name.toLowerCase() === primarySurname.toLowerCase()) continue; const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'given'); if (target) await verifyAndNudge(api, name, 'given name', currentTitle, hasShortDesc, isOrphan, target); } const finalSurnames = surnames.length > 0 ? surnames : (nameParts.length >= 2 ? [nameParts[nameParts.length - 1]] : []); for (const name of finalSurnames) { const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'surname'); if (target) await verifyAndNudge(api, name, 'surname', currentTitle, hasShortDesc, isOrphan, target); } } async function verifyAndNudge(api, name, type, currentTitle, hasShortDesc, isOrphan, foundTitle) { const res = await api.get({ action: 'query', prop: 'revisions', titles: foundTitle, rvprop: 'content', formatversion: 2 }); const page = res.query.pages[0]; if (!page || page.missing || !page.revisions) return; const text = page.revisions[0].content; const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text); const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text); const isNamePageTitle = foundTitle.includes('List of people with'); if (!isNameMatch && !isNameDab && !isNamePageTitle) return; const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]'); const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text); if (alreadyListed) return; const $container = $('<div>').addClass('comrade-container').addClass(isOrphan ? 'comrade-nudge' : 'comrade-info'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text(isOrphan ? 'Comrade nudge:' : 'Informative:')); const $content = $('<div>').addClass('comrade-content'); $content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({ href: mw.util.getUrl(foundTitle), target: '_blank' }).text(foundTitle), "; 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('Consider adding ', $('<code>').text(snippet)); const $copyBtn = $('<button>').text('Copy & insert').on('click', function() { navigator.clipboard.writeText('\n' + snippet).then(() => { $(this).text('Copied!').prop('disabled', true); const editUrl = mw.util.getUrl(foundTitle, { action: 'edit', summary: 'Adding * {{[[Template:Annotated biography link|anbl]]|[[' + currentTitle + ']]}}' }); window.open(editUrl, '_blank'); }); }); $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); ComradeLib.appendDismiss($container); } async function checkNamingPrecision(currentTitle, isHuman) { const disambigMatch = currentTitle.match(/^(.+?)(?:, | \()/); if (isHuman || !disambigMatch) return; const baseTitle = disambigMatch[1].trim(); const api = new mw.Api(); try { const baseRes = await api.get({ action: 'query', titles: baseTitle, redirects: 1, formatversion: 2 }); const page = baseRes.query.pages[0]; const baseExists = !page.missing; let baseRedirectsToSelf = false; if (baseRes.query.redirects) { baseRedirectsToSelf = baseRes.query.redirects.some(r => r.to === currentTitle); } const searchRes = await api.get({ action: 'query', list: 'allpages', apprefix: baseTitle, aplimit: 50, formatversion: 2 }); const competitors = searchRes.query.allpages.filter(p => { const t = p.title; return t !== currentTitle && t !== baseTitle && (t.startsWith(baseTitle + ', ') || t.startsWith(baseTitle + ' (')); }); if (!baseExists || baseRedirectsToSelf) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').addClass('comrade-content'); if (competitors.length === 0) { $header.append($('<strong>').text('Precision:')); const reason = baseRedirectsToSelf ? "already redirects here" : "is free"; $content.append($('<span>').append(`The base name `, $('<code>').text(baseTitle), ` ${reason}. Consider moving this article.`)); const moveUrl = mw.util.getUrl('Special:MovePage/' + currentTitle, { wpNewTitle: baseTitle, wpReason: 'Unnecessary disambiguation; base name is free/redirects here per [[WP:PRECISION]]' }); $content.append($('<button>').text(`Move to ${baseTitle}`).on('click', () => window.open(moveUrl, '_blank'))); } else { $header.append($('<strong>').text('Observation:')); const state = baseRedirectsToSelf ? 'redirect to this page' : 'redlink'; $content.append($('<span>').append(`Base name `, $('<code>').text(baseTitle), ` has ${competitors.length} competitors, but it is currently a ${state}.`)); $content.append($('<span>').text('Consider if a disambiguation page is needed.')); } $container.append($header, $content); ComradeLib.appendDismiss($container); } } catch (e) { console.warn("Comrade: Precision check failed", e); } } async function checkHumanSort(wikitext, entity, currentTitle, tClean, refName) { const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i); const { givens } = await fetchWikidataNameLabels(entity); if (dsMatch) { const currentDS = dsMatch[1].trim(); if (currentDS.includes(',')) { let [lastPart, firstPart] = currentDS.split(',').map(s => s.trim()); if (firstPart.toLowerCase().endsWith(lastPart.toLowerCase())) { let fixedFirst = firstPart.substring(0, firstPart.length - lastPart.length).trim(); let suggestion = `${fixNameCasing(lastPart, refName)}, ${fixNameCasing(fixedFirst, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, "Redundant surname in given name field."); } else if (givens.length > 0 && lastPart.split(' ').length > 1) { const parts = lastPart.split(' '); const misplacedGiven = parts.find(part => givens.some(gn => gn.toLowerCase() === part.toLowerCase())); if (misplacedGiven) { const newSurname = parts.filter(p => p !== misplacedGiven).join(' '); let suggestion = `${fixNameCasing(newSurname, refName)}, ${fixNameCasing(misplacedGiven + ' ' + firstPart, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, `Wikidata suggests "${misplacedGiven}" is a given name.`); } } } } else { const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const nameParts = tClean.split(' '); const targetSortName = hasFamilyNameHatnote ? `${nameParts[0]}, ${nameParts.slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { const $dsMissing = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Add: {{DEFAULTSORT:${targetSortName}}}`).on('click', () => performSortCorrection(targetSortName, "add")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Tag is missing. `, $btn)); $dsMissing.append($header); ComradeLib.appendDismiss($dsMissing); } } } async function fetchWikidataEntity(qid) { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: qid, props: 'claims|labels', languages: 'en', format: 'json', origin: '*' }); try { const wikiData = await fetch(url).then(r => r.json()); return wikiData.entities[qid]; } catch (e) { return null; } } function getSmartSortKey(fullName, entity) { const countryIds = entity.claims?.P27?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const locationIds = entity.claims?.P17?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const placeIds = [...(entity.claims?.P19 || []), ...(entity.claims?.P119 || [])].map(c => c.mainsnak?.datavalue?.value?.id).filter(Boolean); const allRelevantIds = [...countryIds, ...locationIds, ...placeIds]; const birthYear = parseInt(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.match(/[+-](\d{4})/)?.[1]) || null; if (fullName.startsWith("Saint ")) return fullName.replace("Saint ", ""); const arabicParticles = /\b(Abu|Abd|Abdel|Abdul|ben|bin|bint)\b/i; if (fullName.match(arabicParticles)) { if (birthYear && birthYear > 1900) { const parts = fullName.split(' '); const binIndex = parts.findIndex(p => p.toLowerCase() === 'bin' || p.toLowerCase() === 'ben'); if (binIndex > 0) return parts.slice(binIndex).join(' ') + ', ' + parts.slice(0, binIndex).join(' '); } else { return fullName; } } const eastAsianQids = ["Q148", "Q865", "Q884", "Q424", "Q17", "Q881"]; if (allRelevantIds.some(id => eastAsianQids.includes(id))) { if (allRelevantIds.includes("Q17") && birthYear && birthYear > 1885) return westernSort(fullName); const parts = fullName.split(' '); if (parts.length > 1) return `${parts[0]}, ${parts.slice(1).join(' ')}`; } return westernSort(fullName); } function westernSort(name) { let cleanName = name; if (name.startsWith("O'")) cleanName = name.replace("O'", "O"); const parts = cleanName.split(' '); if (parts.length < 2) return cleanName; const surname = parts.pop(); if (["Jr.", "Jr", "III", "II", "Sr.", "Sr"].includes(surname)) { const actualSurname = parts.pop(); return `${actualSurname}, ${parts.join(' ')} ${surname}`; } return `${surname}, ${parts.join(' ')}`; } async function performSortCorrection(newName, type) { const api = new mw.Api(); const title = mw.config.get("wgPageName"); try { const data = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = data.query.pages[0].revisions[0].content; const dsRegex = /\{\{\s*(?:DEFAULTSORT|Defaultsort)\s*:[^}]+\}\}/i; const newTag = `{{DEFAULTSORT:${newName}}}`; if (dsRegex.test(content)) { content = content.replace(dsRegex, newTag); } else { const catRegex = /\[\[Category:/i; content = catRegex.test(content) ? content.replace(catRegex, `${newTag}\n[[Category:`) : content.trim() + `\n\n${newTag}`; } await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Setting DEFAULTSORT to ${newName}`, nocreate: true }); mw.notify(`DEFAULTSORT ${type === "add" ? "added" : "corrected"} to ${newName}!`, { title: 'Comrade Success', type: 'success' }); setTimeout(() => location.reload(), 700); } catch (err) { mw.notify('Failed to save DEFAULTSORT.', { type: 'error' }); } } function renderSortNudge(target, reason) { const $dsNudge = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Correct to: ${target}`).on('click', () => performSortCorrection(target, "format")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` ${reason} `, $btn)); $dsNudge.append($header); ComradeLib.appendDismiss($dsNudge); } async function fetchWikidataNameLabels(entity) { const familyIds = entity.claims?.P734?.map(c => c.mainsnak.datavalue.value.id) || []; const givenIds = entity.claims?.P735?.map(c => c.mainsnak.datavalue.value.id) || []; const allNameIds = [...new Set([...familyIds, ...givenIds])]; if (allNameIds.length === 0) return { givens: [], surnames: [] }; const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: allNameIds.join('|'), props: 'labels', languages: 'en', format: 'json', origin: '*' }); const nameData = await fetch(url).then(r => r.json()); return { givens: givenIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean), surnames: familyIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean) }; } function fixNameCasing(part, referenceName) { return part.split(' ').map(word => { const match = referenceName.match(new RegExp(`\\b${word}\\b`, 'i')); if (match) return match[0]; return referenceName.split(' ').find(tWord => word.toLowerCase().startsWith(tWord.toLowerCase()) || tWord.toLowerCase().startsWith(word.toLowerCase())) || word; }).join(' '); } async function init() { const api = new mw.Api(); const ns = mw.config.get("wgNamespaceNumber"); const action = mw.config.get("wgAction"); if (mw.config.get("wgDBname") !== "enwiki" || action !== "view") return; if (ns === 14) { const $btn = $('<button>').attr('id', 'comrade-audit-btn').text('Audit category quality').css({ 'margin-bottom': '10px', 'padding': '5px 10px', 'cursor': 'pointer' }).on('click', ComradeLib.performCategoryAudit); $('.mw-category-generated').prepend($btn); return; } if (ns !== 0) return; const qid = mw.config.get("wgWikibaseItemId"); const currentTitle = mw.config.get("wgTitle"); 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; // Ligature/ASCII-only/diacritic and domain const norms = ComradeLib.getNormalizedTitles(currentTitle); if (norms.ascii) { let label = "ASCII-only"; let category = "R from ASCII-only"; if (/[æÆœŒijIJßẞ]/.test(currentTitle)) { label = "Ligature"; category = "R to ligature"; } else if (currentTitle.normalize("NFD").match(/[\u0300-\u036f]/)) { label = "Diacritic"; category = "R to diacritic"; } await ComradeLib.checkAndRenderRedirect(norms.ascii, currentTitle, category, label); } await ComradeLib.checkDomainRedirect(qid, currentTitle); // Orphan logic 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 $btn = $('<button>').text('Remove orphan tag').on('click', async () => await ComradeLib.performDeorphan()); const $statusText = $('<span>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn); $container.append($('<div>').addClass('comrade-header').append($statusText)); ComradeLib.appendDismiss($container); } // Wikidata/human logic let entity = qid ? await fetchWikidataEntity(qid) : null; let isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5"); await checkNamingPrecision(currentTitle, isHuman); if (isHuman && entity) { const tClean = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim(); const refName = entity.labels?.en?.value || currentTitle; const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext); const isOrphan = isOrphanTagged && backLinkCount < 1; await ComradeLib.checkShortDescDates(wikitext, entity); // Check DEFAULTSORT await checkHumanSort(wikitext, entity, currentTitle, tClean, refName); const longName = ComradeLib.getLongNameFromWikitext(wikitext); if (longName && longName.length > tClean.length) { let sortKey = hasFamilyNameHatnote ? `${longName.split(' ')[0]}, ${longName.split(' ').slice(1).join(' ')}` : getSmartSortKey(longName, entity); const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`; await ComradeLib.checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext); } // Sort name R const targetSortName = hasFamilyNameHatnote ? `${tClean.split(' ')[0]}, ${tClean.split(' ').slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName && targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { let targetPage = currentTitle.includes(' (') ? currentTitle.split(' (')[0] : currentTitle; let rCat = targetPage !== currentTitle ? "R from ambiguous sort name" : "R from sort name"; if (targetSortName.includes(',')) { const p = targetSortName.split(','); rCat += `|${p[0].trim().charAt(0).toUpperCase()}|${p[1].trim().charAt(0).toUpperCase()}`; } await ComradeLib.checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name"); } // Name lists await processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean); } // Quality/ORES const oresWait = setInterval(async () => { if ($('.article_quality').length) { clearInterval(oresWait); await ComradeLib.checkQualityConsistency(); } }, 500); setTimeout(() => clearInterval(oresWait), 5000); } catch (err) { console.error("Comrade error:", err); } } init(); })(); (function() { if (window.Headmaster) return; const VERSION = "0.9.2.1"; const defaultNS = [0, 118]; const userNS = window.headmaster_ns || []; const config = { menu: window.headmaster_menu || 'p-cactions', enableSummary: window.headmaster_do_summary !== false, customSummary: window.headmaster_summary || "", allowedNamespaces: Array.from(new Set([...defaultNS, ...userNS])), autoScan: window.headmaster_auto_scan !== false, autoRun: window.headmaster_auto_run === false }; const Headmaster = { VERSION: VERSION, DefaultSummary: "Converting [[MOS:PSEUDOHEAD|pseudo-headings]] {details}using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]. Semicolon markup is reserved for [[MOS:DLIST|description lists]].", styleInjected: false, css: ` #hm-panel { clear: both; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; margin-bottom: 1.5em; border-radius: 2px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } #hm-table { width: 100%; border-collapse: collapse; background: white; margin-top: 10px; } #hm-table th { background: #f2f2f2; border: 1px solid #a2a9b1; padding: 8px; text-align: left; } #hm-table td { border: 1px solid #a2a9b1; padding: 8px; vertical-align: middle; } .hm-context { font-family: 'Courier New', monospace; font-size: 0.9em; color: #54595d; background: #fdfdfd; } #hm-controls { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; } #hm-controls button { margin-left: 10px; padding: 6px 16px; cursor: pointer; border-radius: 2px; } .hm-btn-apply { background: #36c; color: white; border: 1px solid #36c; font-weight: bold; } .hm-btn-apply:hover { background: #447ff5; } .hm-global-selector { font-size: 0.9em; color: #202122; background: #eaecf0; padding: 8px; border-radius: 2px; border: 1px solid #a2a9b1; } .hm-global-selector a { font-weight: bold; text-decoration: none; color: #36c; } .hm-global-selector a:hover { text-decoration: underline; } .hm-locate-btn { cursor: pointer; color: #36c; border: 1px solid #a2a9b1; background: #f8f9fa; padding: 2px 6px; font-size: 0.85em; border-radius: 2px; } .hm-locate-btn:hover { background: #fff; border-color: #36c; } `, setup() { if (!config.allowedNamespaces.includes(mw.config.get("wgNamespaceNumber"))) return; const action = mw.config.get("wgAction"); this.addPortlet(action); if (action === 'view' && (config.autoScan || config.autoRun)) { this.silentScan(); } if (mw.util.getParamValue('headmaster') === '1' && ['edit', 'submit'].includes(action)) { const params = new URLSearchParams(window.location.search); params.delete('headmaster'); const searchString = params.toString(); const newUrl = window.location.pathname + (searchString ? '?' + searchString : ''); window.history.replaceState(null, '', newUrl); this.init(); } }, addPortlet(action) { const link = mw.util.addPortletLink(config.menu, '#', 'Headmaster', 'ca-hm', 'Interactively convert pseudo-headings'); if (link) { $(link).on('click', (e) => { e.preventDefault(); if (action === 'view') { this.redirectToEdit(); } else if (['edit', 'submit'].includes(action)) { this.init(); } }); } }, redirectToEdit() { window.location.href = mw.util.getUrl(mw.config.get("wgPageName"), { action: 'edit', headmaster: '1' }); }, silentScan() { new mw.Api().get({ action: 'query', prop: 'revisions', titles: mw.config.get('wgPageName'), rvprop: 'content', rvlimit: 1, formatversion: 2 }).done((data) => { const page = data.query.pages[0]; if (!page || !page.revisions) return; const wikitext = page.revisions[0].content; const tasks = this.analyzeText(wikitext); if (tasks.length > 0) { if (config.autoRun) { this.redirectToEdit(); } else { this.notifyDiscovery(tasks.length); } } }).fail((err) => { mw.log.warn("Headmaster: silentScan failed", err); }); }, notifyDiscovery(count) { const $msg = $('<span>').text(`Headmaster: Found ${count} issues. `).append($('<a>').text('Run conversion').css({ fontWeight: 'bold', cursor: 'pointer' }).on('click', () => this.redirectToEdit())); mw.notify($msg, { tag: 'hm-discovery', autoHide: false }); }, init() { if (!this.styleInjected) { mw.loader.addStyleTag(this.css); this.styleInjected = true; } const $textbox = $('#wpTextbox1'); const wikitext = $textbox.textSelection('getContents'); if (!wikitext) { mw.notify("Textbox is empty or not found.", { type: 'error' }); return; } const tasks = this.analyzeText(wikitext); if (tasks.length === 0) { mw.notify("No issues found."); return; } this.showUI(tasks, wikitext); }, analyzeText(wikitext) { const lines = wikitext.split("\n"); const tasks = []; let currentDepth = 1; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const headingMatch = line.match(/^(={2,6})\s*(.+?)\s*\1\s*$/); if (headingMatch) { currentDepth = headingMatch[1].length; const prevLine = i > 0 ? lines[i - 1].trim() : null; const isSmashed = prevLine !== null && prevLine !== ""; const isExtraSpace = i > 1 && lines[i - 1].trim() === "" && lines[i - 2].trim() === ""; if (isSmashed || isExtraSpace) { tasks.push({ index: i, type: 'WHITESPACE', original: lines[i], isSmashed: isSmashed, isExtraSpace: isExtraSpace }); } } else if (line.startsWith(';') && !line.startsWith(';;')) { const nextLine = lines[i + 1] || ""; if (!nextLine.trim().startsWith(':')) { const cleanText = line.substring(1).replace(/\s*:.*$/, "").trim(); const suggestedLevel = "=".repeat(Math.min(currentDepth + 1, 6)); tasks.push({ index: i, type: 'PSEUDO', original: line, cleanText: cleanText, context: nextLine.substring(0, 60).trim() + (nextLine.length > 60 ? "..." : ""), suggestedLevel: suggestedLevel }); } } } return tasks; }, showUI(tasks, originalWikitext) { $('#hm-panel').remove(); $('#editform').hide(); const visibleTasks = tasks.filter(t => t.type === 'PSEUDO'); const pseudoCount = visibleTasks.length; const whitespaceCount = tasks.filter(t => t.type === 'WHITESPACE').length; let statusMsg = ""; const s = (n) => n !== 1 ? 's' : ''; if (pseudoCount > 0) { statusMsg = `Found <strong>${pseudoCount}</strong> possible pseudo-heading${s(pseudoCount)}.`; if (whitespaceCount > 0) { statusMsg += ` Also fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } } else { statusMsg = `Did not find any pseudo-headings, but fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } const tableRows = pseudoCount > 0 ? visibleTasks.map((task, i) => ` <tr data-task-index="${task.index}"> <td><button class="hm-locate-btn" data-hm-i="${tasks.indexOf(task)}" title="Show in editor">Locate</button></td> <td class="hm-context"><code>${mw.html.escape(task.original)}</code></td> <td><label><input type="radio" name="hm-opt-${i}" value="keep"> Keep</label></td> <td><label><input type="radio" name="hm-opt-${i}" value="bold" checked> <b>Bold</b></label></td> <td><label><input type="radio" name="hm-opt-${i}" value="head"> Heading (${task.suggestedLevel.length})</label></td> <td class="hm-context">${mw.html.escape(task.context || "(end of page)")}</td> </tr> `).join("") : `<tr><td colspan="6" style="text-align:center; padding: 2em; color: #54595d; background: #f8f9fa;"> No interactive heading changes detected. Only background spacing normalization will be applied. </td></tr>`; const $panel = $(` <div id="hm-panel"> <h3 style="margin-top:0">Headmaster v${this.VERSION}: Review pseudo-headings</h3> <div class="hm-global-selector" ${pseudoCount === 0 ? 'style="display:none"' : ''}> <strong>Bulk action:</strong> <a href="#" id="hm-all-keep">Set all to Keep</a> | <a href="#" id="hm-all-bold">Set all to Bold</a> | <a href="#" id="hm-all-head">Set all to Heading</a> </div> <table id="hm-table"> <thead> <tr> <th>Find</th> <th>Source</th> <th colspan="3">Action</th> <th>Next line context</th> </tr> </thead> <tbody>${tableRows}</tbody> </table> <div id="hm-controls"> <span>${statusMsg}</span> <div> <button id="hm-cancel" class="mw-ui-button">Cancel</button> <button id="hm-apply" class="hm-btn-apply">Show changes (diff)</button> </div> </div> </div> `); $('#content').prepend($panel); window.scrollTo(0, 0); $('.hm-locate-btn').on('click', function() { $(document).off('mousedown.hmlocate'); const idx = $(this).data('hm-i'); const task = tasks[idx]; const lines = originalWikitext.split('\n'); let charOffset = 0; for (let j = 0; j < task.index; j++) { charOffset += lines[j].length + 1; } $('#hm-panel').hide(); $('#editform').show(); const $textbox = $('#wpTextbox1'); $textbox.textSelection('setSelection', { start: charOffset, end: charOffset + lines[task.index].length }); $textbox.textSelection('scrollToCaretPosition'); const locateNotify = mw.notify("Locating line " + (task.index + 1) + ". Click outside the editor to return.", { tag: 'hm-locate', type: 'success', autoHide: false }); setTimeout(() => { $(document).on('mousedown.hmlocate', function(e) { if (!$(e.target).closest('#editform, .mw-notification-area').length) { $(document).off('mousedown.hmlocate'); Promise.resolve(locateNotify).then(n => n.close()); $('#editform').hide(); $('#hm-panel').show(); } }); }, 200); }); $('#hm-all-keep').on('click', (e) => { e.preventDefault(); $('input[value="keep"]').prop('checked', true); }); $('#hm-all-bold').on('click', (e) => { e.preventDefault(); $('input[value="bold"]').prop('checked', true); }); $('#hm-all-head').on('click', (e) => { e.preventDefault(); $('input[value="head"]').prop('checked', true); }); $('#hm-cancel').on('click', () => { $('#hm-panel').remove(); $('#editform').show(); }); $('#hm-apply').on('click', () => this.applyChanges(tasks, originalWikitext)); }, getProcessedSummary(existingSummary, bolds, heads, spaces) { if (!config.enableSummary) return existingSummary; let mySummary = ""; let isDefault = false; if (config.customSummary) { mySummary = config.customSummary; } else { isDefault = true; if (bolds > 0 || heads > 0) { const detailsArr = []; if (bolds > 0) detailsArr.push(`${bolds} to bold`); if (heads > 0) detailsArr.push(`${heads} to heading`); const detailsStr = detailsArr.length > 0 ? `(${detailsArr.join(', ')}) ` : ""; mySummary = this.DefaultSummary.replace("{details}", detailsStr); if (spaces > 0) { mySummary += " Also handled [[MOS:BLANKLINE]]."; } } else if (spaces > 0) { mySummary = "Handling [[MOS:BLANKLINE]] using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]"; } } if (!mySummary) return existingSummary; const trimmedOld = existingSummary.trim(); if (!trimmedOld) return mySummary; if (/[.!?]$/.test(trimmedOld)) { return trimmedOld + " " + mySummary; } else { const finalSummary = isDefault ? mySummary.charAt(0).toLowerCase() + mySummary.slice(1) : mySummary; return trimmedOld + "; " + finalSummary; } }, applyChanges(tasks, wikitext) { try { let lines = wikitext.split("\n"); let bCount = 0, hCount = 0, wCount = 0; const pseudoTasks = tasks.filter(t => t.type === 'PSEUDO'); for (let i = pseudoTasks.length - 1; i >= 0; i--) { const task = pseudoTasks[i]; const idx = task.index; const action = $('#hm-table tbody tr').filter(`[data-task-index="${idx}"]`).find('input:checked').val(); if (action === 'bold') { let newValue = `'''${task.cleanText}'''`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; bCount++; } else if (action === 'head') { const h = task.suggestedLevel; let newValue = `${h} ${task.cleanText} ${h}`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; hCount++; } } const spaceTasks = tasks.filter(t => t.type === 'WHITESPACE'); for (let i = spaceTasks.length - 1; i >= 0; i--) { const task = spaceTasks[i]; const idx = task.index; if (task.isSmashed) { lines.splice(idx, 0, ""); } else if (task.isExtraSpace) { let backIdx = idx - 1; while (backIdx > 0 && lines[backIdx].trim() === "" && lines[backIdx - 1].trim() === "") { lines.splice(backIdx, 1); backIdx--; } } wCount++; } if (bCount + hCount + wCount === 0) { mw.notify("No changes selected."); $('#hm-panel').remove(); $('#editform').show(); return; } $('#wpTextbox1').textSelection('setContents', lines.join("\n")); const $summary = $('#wpSummary'); $summary.val(this.getProcessedSummary($summary.val(), bCount, hCount, wCount)); $('#hm-panel').remove(); $('#editform').show(); mw.notify(`Headmaster: Fixed ${wCount} spacing issue(s) and converted ${bCount + hCount} heading(s).`); $('html, body').animate({ scrollTop: 0 }, 'fast'); $('#wpDiff').click(); } catch (err) { $('#editform').show(); mw.notify("Headmaster error: " + err.message, { type: 'error' }); } } }; window.Headmaster = Headmaster; mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => { $(() => Headmaster.setup()); }); })(); //</nowiki> jzs9nucd3tk9xydji0taz4t1z1fempn 739834 739829 2026-04-30T06:41:40Z Sam Sailor 26820 Test 739834 javascript text/javascript //<nowiki> /* global ComradeLib, mw, $ */ (async function() { await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-lib.js&action=raw&ctype=text/javascript'); async function getTargetPage(api, name, type) { const titles = type === 'surname' ? [`List of people with the English surname ${name}`, `List of people with surname ${name}`, `${name} (surname)`, `${name} (name)`, name] : [`List of people with given name ${name}`, `List of people named ${name}`, `${name} (given name)`, `${name} (name)`, name]; const res = await api.get({ action: 'query', titles: titles.join('|'), formatversion: 2 }); if (!res.query || !res.query.pages) return null; return titles.find(t => { const pg = res.query.pages.find(p => p.title === t); return pg && !pg.missing; }); } async function processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean) { const { givens, surnames } = await fetchWikidataNameLabels(entity); const nameParts = tClean.split(' '); const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const primarySurname = hasFamilyNameHatnote ? nameParts[0] : (getSmartSortKey(tClean, entity).split(',')[0] || nameParts[nameParts.length - 1]); for (const name of givens) { if (name.toLowerCase() === primarySurname.toLowerCase()) continue; const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'given'); if (target) await verifyAndNudge(api, name, 'given name', currentTitle, hasShortDesc, isOrphan, target); } const finalSurnames = surnames.length > 0 ? surnames : (nameParts.length >= 2 ? [nameParts[nameParts.length - 1]] : []); for (const name of finalSurnames) { const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'surname'); if (target) await verifyAndNudge(api, name, 'surname', currentTitle, hasShortDesc, isOrphan, target); } } async function verifyAndNudge(api, name, type, currentTitle, hasShortDesc, isOrphan, foundTitle) { const res = await api.get({ action: 'query', prop: 'revisions', titles: foundTitle, rvprop: 'content', formatversion: 2 }); const page = res.query.pages[0]; if (!page || page.missing || !page.revisions) return; const text = page.revisions[0].content; const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text); const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text); const isNamePageTitle = foundTitle.includes('List of people with'); if (!isNameMatch && !isNameDab && !isNamePageTitle) return; const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]'); const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text); if (alreadyListed) return; const $container = $('<div>').addClass('comrade-container').addClass(isOrphan ? 'comrade-nudge' : 'comrade-info'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text(isOrphan ? 'Comrade nudge:' : 'Informative:')); const $content = $('<div>').addClass('comrade-content'); $content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({ href: mw.util.getUrl(foundTitle), target: '_blank' }).text(foundTitle), "; 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('Consider adding ', $('<code>').text(snippet)); const $copyBtn = $('<button>').text('Copy & insert').on('click', function() { navigator.clipboard.writeText('\n' + snippet).then(() => { $(this).text('Copied!').prop('disabled', true); const editUrl = mw.util.getUrl(foundTitle, { action: 'edit', summary: 'Adding * {{[[Template:Annotated biography link|anbl]]|[[' + currentTitle + ']]}}' }); window.open(editUrl, '_blank'); }); }); $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); ComradeLib.appendDismiss($container); } async function checkNamingPrecision(currentTitle, isHuman) { const disambigMatch = currentTitle.match(/^(.+?)(?:, | \()/); if (isHuman || !disambigMatch) return; const baseTitle = disambigMatch[1].trim(); const api = new mw.Api(); try { const baseRes = await api.get({ action: 'query', titles: baseTitle, redirects: 1, formatversion: 2 }); const page = baseRes.query.pages[0]; const baseExists = !page.missing; let baseRedirectsToSelf = false; if (baseRes.query.redirects) { baseRedirectsToSelf = baseRes.query.redirects.some(r => r.to === currentTitle); } const searchRes = await api.get({ action: 'query', list: 'allpages', apprefix: baseTitle, aplimit: 50, formatversion: 2 }); const competitors = searchRes.query.allpages.filter(p => { const t = p.title; return t !== currentTitle && t !== baseTitle && (t.startsWith(baseTitle + ', ') || t.startsWith(baseTitle + ' (')); }); if (!baseExists || baseRedirectsToSelf) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').addClass('comrade-content'); if (competitors.length === 0) { $header.append($('<strong>').text('Precision:')); const reason = baseRedirectsToSelf ? "already redirects here" : "is free"; $content.append($('<span>').append(`The base name `, $('<code>').text(baseTitle), ` ${reason}. Consider moving this article.`)); const moveUrl = mw.util.getUrl('Special:MovePage/' + currentTitle, { wpNewTitle: baseTitle, wpReason: 'Unnecessary disambiguation; base name is free/redirects here per [[WP:PRECISION]]' }); $content.append($('<button>').text(`Move to ${baseTitle}`).on('click', () => window.open(moveUrl, '_blank'))); } else { $header.append($('<strong>').text('Observation:')); const state = baseRedirectsToSelf ? 'redirect to this page' : 'redlink'; $content.append($('<span>').append(`Base name `, $('<code>').text(baseTitle), ` has ${competitors.length} competitors, but it is currently a ${state}.`)); $content.append($('<span>').text('Consider if a disambiguation page is needed.')); } $container.append($header, $content); ComradeLib.appendDismiss($container); } } catch (e) { console.warn("Comrade: Precision check failed", e); } } async function checkHumanSort(wikitext, entity, currentTitle, tClean, refName) { const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i); const { givens } = await fetchWikidataNameLabels(entity); if (dsMatch) { const currentDS = dsMatch[1].trim(); if (currentDS.includes(',')) { let [lastPart, firstPart] = currentDS.split(',').map(s => s.trim()); if (firstPart.toLowerCase().endsWith(lastPart.toLowerCase())) { let fixedFirst = firstPart.substring(0, firstPart.length - lastPart.length).trim(); let suggestion = `${fixNameCasing(lastPart, refName)}, ${fixNameCasing(fixedFirst, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, "Redundant surname in given name field."); } else if (givens.length > 0 && lastPart.split(' ').length > 1) { const parts = lastPart.split(' '); const misplacedGiven = parts.find(part => givens.some(gn => gn.toLowerCase() === part.toLowerCase())); if (misplacedGiven) { const newSurname = parts.filter(p => p !== misplacedGiven).join(' '); let suggestion = `${fixNameCasing(newSurname, refName)}, ${fixNameCasing(misplacedGiven + ' ' + firstPart, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, `Wikidata suggests "${misplacedGiven}" is a given name.`); } } } } else { const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const nameParts = tClean.split(' '); const targetSortName = hasFamilyNameHatnote ? `${nameParts[0]}, ${nameParts.slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { const $dsMissing = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Add: {{DEFAULTSORT:${targetSortName}}}`).on('click', () => performSortCorrection(targetSortName, "add")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Tag is missing. `, $btn)); $dsMissing.append($header); ComradeLib.appendDismiss($dsMissing); } } } async function fetchWikidataEntity(qid) { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: qid, props: 'claims|labels', languages: 'en', format: 'json', origin: '*' }); try { const wikiData = await fetch(url).then(r => r.json()); return wikiData.entities[qid]; } catch (e) { return null; } } function getSmartSortKey(fullName, entity) { const countryIds = entity.claims?.P27?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const locationIds = entity.claims?.P17?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const placeIds = [...(entity.claims?.P19 || []), ...(entity.claims?.P119 || [])].map(c => c.mainsnak?.datavalue?.value?.id).filter(Boolean); const allRelevantIds = [...countryIds, ...locationIds, ...placeIds]; const birthYear = parseInt(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.match(/[+-](\d{4})/)?.[1]) || null; if (fullName.startsWith("Saint ")) return fullName.replace("Saint ", ""); const arabicParticles = /\b(Abu|Abd|Abdel|Abdul|ben|bin|bint)\b/i; if (fullName.match(arabicParticles)) { if (birthYear && birthYear > 1900) { const parts = fullName.split(' '); const binIndex = parts.findIndex(p => p.toLowerCase() === 'bin' || p.toLowerCase() === 'ben'); if (binIndex > 0) return parts.slice(binIndex).join(' ') + ', ' + parts.slice(0, binIndex).join(' '); } else { return fullName; } } const eastAsianQids = ["Q148", "Q865", "Q884", "Q424", "Q17", "Q881"]; if (allRelevantIds.some(id => eastAsianQids.includes(id))) { if (allRelevantIds.includes("Q17") && birthYear && birthYear > 1885) return westernSort(fullName); const parts = fullName.split(' '); if (parts.length > 1) return `${parts[0]}, ${parts.slice(1).join(' ')}`; } return westernSort(fullName); } function westernSort(name) { let cleanName = name; if (name.startsWith("O'")) cleanName = name.replace("O'", "O"); const parts = cleanName.split(' '); if (parts.length < 2) return cleanName; const surname = parts.pop(); if (["Jr.", "Jr", "III", "II", "Sr.", "Sr"].includes(surname)) { const actualSurname = parts.pop(); return `${actualSurname}, ${parts.join(' ')} ${surname}`; } return `${surname}, ${parts.join(' ')}`; } async function performSortCorrection(newName, type) { const api = new mw.Api(); const title = mw.config.get("wgPageName"); try { const data = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = data.query.pages[0].revisions[0].content; const dsRegex = /\{\{\s*(?:DEFAULTSORT|Defaultsort)\s*:[^}]+\}\}/i; const newTag = `{{DEFAULTSORT:${newName}}}`; if (dsRegex.test(content)) { content = content.replace(dsRegex, newTag); } else { const catRegex = /\[\[Category:/i; content = catRegex.test(content) ? content.replace(catRegex, `${newTag}\n[[Category:`) : content.trim() + `\n\n${newTag}`; } await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Setting DEFAULTSORT to ${newName}`, nocreate: true }); mw.notify(`DEFAULTSORT ${type === "add" ? "added" : "corrected"} to ${newName}!`, { title: 'Comrade Success', type: 'success' }); setTimeout(() => location.reload(), 700); } catch (err) { mw.notify('Failed to save DEFAULTSORT.', { type: 'error' }); } } function renderSortNudge(target, reason) { const $dsNudge = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Correct to: ${target}`).on('click', () => performSortCorrection(target, "format")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` ${reason} `, $btn)); $dsNudge.append($header); ComradeLib.appendDismiss($dsNudge); } async function fetchWikidataNameLabels(entity) { const familyIds = entity.claims?.P734?.map(c => c.mainsnak.datavalue.value.id) || []; const givenIds = entity.claims?.P735?.map(c => c.mainsnak.datavalue.value.id) || []; const allNameIds = [...new Set([...familyIds, ...givenIds])]; if (allNameIds.length === 0) return { givens: [], surnames: [] }; const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: allNameIds.join('|'), props: 'labels', languages: 'en', format: 'json', origin: '*' }); const nameData = await fetch(url).then(r => r.json()); return { givens: givenIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean), surnames: familyIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean) }; } function fixNameCasing(part, referenceName) { return part.split(' ').map(word => { const match = referenceName.match(new RegExp(`\\b${word}\\b`, 'i')); if (match) return match[0]; return referenceName.split(' ').find(tWord => word.toLowerCase().startsWith(tWord.toLowerCase()) || tWord.toLowerCase().startsWith(word.toLowerCase())) || word; }).join(' '); } async function init() { const api = new mw.Api(); const ns = mw.config.get("wgNamespaceNumber"); const action = mw.config.get("wgAction"); if (mw.config.get("wgDBname") !== "enwiki" || action !== "view") return; if (ns === 14) { const $btn = $('<button>').attr('id', 'comrade-audit-btn').text('Audit category quality').css({ 'margin-bottom': '10px', 'padding': '5px 10px', 'cursor': 'pointer' }).on('click', ComradeLib.performCategoryAudit); $('.mw-category-generated').prepend($btn); return; } if (ns !== 0) return; const qid = mw.config.get("wgWikibaseItemId"); const currentTitle = mw.config.get("wgTitle"); 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; // Ligature/ASCII-only/diacritic and domain const norms = ComradeLib.getNormalizedTitles(currentTitle); if (norms.ascii) { let label = "ASCII-only"; let category = "R from ASCII-only"; if (/[æÆœŒijIJßẞ]/.test(currentTitle)) { label = "Ligature"; category = "R to ligature"; } else if (currentTitle.normalize("NFD").match(/[\u0300-\u036f]/)) { label = "Diacritic"; category = "R to diacritic"; } await ComradeLib.checkAndRenderRedirect(norms.ascii, currentTitle, category, label); } await ComradeLib.checkDomainRedirect(qid, currentTitle); // Orphan logic 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 $btn = $('<button>').text('Remove orphan tag').on('click', async () => await ComradeLib.performDeorphan()); const $statusText = $('<span>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn); $container.append($('<div>').addClass('comrade-header').append($statusText)); ComradeLib.appendDismiss($container); } // Wikidata/human logic let entity = qid ? await fetchWikidataEntity(qid) : null; let isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5"); await checkNamingPrecision(currentTitle, isHuman); if (isHuman && entity) { const tClean = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim(); const refName = entity.labels?.en?.value || currentTitle; const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext); const isOrphan = isOrphanTagged && backLinkCount < 1; await ComradeLib.checkShortDescDates(wikitext, entity); // Check DEFAULTSORT await checkHumanSort(wikitext, entity, currentTitle, tClean, refName); const longName = ComradeLib.getLongNameFromWikitext(wikitext); if (longName && longName.length > tClean.length) { let sortKey = hasFamilyNameHatnote ? `${longName.split(' ')[0]}, ${longName.split(' ').slice(1).join(' ')}` : getSmartSortKey(longName, entity); const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`; await ComradeLib.checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext); } // Sort name R const targetSortName = hasFamilyNameHatnote ? `${tClean.split(' ')[0]}, ${tClean.split(' ').slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName && targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { let targetPage = currentTitle.includes(' (') ? currentTitle.split(' (')[0] : currentTitle; let rCat = targetPage !== currentTitle ? "R from ambiguous sort name" : "R from sort name"; if (targetSortName.includes(',')) { const p = targetSortName.split(','); rCat += `|${p[0].trim().charAt(0).toUpperCase()}|${p[1].trim().charAt(0).toUpperCase()}`; } await ComradeLib.checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name"); } // Name lists await processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean); } // Quality/ORES const qualityTarget = document.querySelector('#mw-content-text'); if (qualityTarget) { const observer = new MutationObserver((mutations, obs) => { const $oresElement = $('.article_quality'); if ($oresElement.length && $oresElement.text().trim().length > 0) { obs.disconnect(); ComradeLib.checkQualityConsistency(); } }); observer.observe(qualityTarget, { childList: true, subtree: true }); setTimeout(() => observer.disconnect(), 10000); } } catch (err) { console.error("Comrade error:", err); } } init(); })(); (function() { if (window.Headmaster) return; const VERSION = "0.9.2.1"; const defaultNS = [0, 118]; const userNS = window.headmaster_ns || []; const config = { menu: window.headmaster_menu || 'p-cactions', enableSummary: window.headmaster_do_summary !== false, customSummary: window.headmaster_summary || "", allowedNamespaces: Array.from(new Set([...defaultNS, ...userNS])), autoScan: window.headmaster_auto_scan !== false, autoRun: window.headmaster_auto_run === false }; const Headmaster = { VERSION: VERSION, DefaultSummary: "Converting [[MOS:PSEUDOHEAD|pseudo-headings]] {details}using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]. Semicolon markup is reserved for [[MOS:DLIST|description lists]].", styleInjected: false, css: ` #hm-panel { clear: both; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; margin-bottom: 1.5em; border-radius: 2px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } #hm-table { width: 100%; border-collapse: collapse; background: white; margin-top: 10px; } #hm-table th { background: #f2f2f2; border: 1px solid #a2a9b1; padding: 8px; text-align: left; } #hm-table td { border: 1px solid #a2a9b1; padding: 8px; vertical-align: middle; } .hm-context { font-family: 'Courier New', monospace; font-size: 0.9em; color: #54595d; background: #fdfdfd; } #hm-controls { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; } #hm-controls button { margin-left: 10px; padding: 6px 16px; cursor: pointer; border-radius: 2px; } .hm-btn-apply { background: #36c; color: white; border: 1px solid #36c; font-weight: bold; } .hm-btn-apply:hover { background: #447ff5; } .hm-global-selector { font-size: 0.9em; color: #202122; background: #eaecf0; padding: 8px; border-radius: 2px; border: 1px solid #a2a9b1; } .hm-global-selector a { font-weight: bold; text-decoration: none; color: #36c; } .hm-global-selector a:hover { text-decoration: underline; } .hm-locate-btn { cursor: pointer; color: #36c; border: 1px solid #a2a9b1; background: #f8f9fa; padding: 2px 6px; font-size: 0.85em; border-radius: 2px; } .hm-locate-btn:hover { background: #fff; border-color: #36c; } `, setup() { if (!config.allowedNamespaces.includes(mw.config.get("wgNamespaceNumber"))) return; const action = mw.config.get("wgAction"); this.addPortlet(action); if (action === 'view' && (config.autoScan || config.autoRun)) { this.silentScan(); } if (mw.util.getParamValue('headmaster') === '1' && ['edit', 'submit'].includes(action)) { const params = new URLSearchParams(window.location.search); params.delete('headmaster'); const searchString = params.toString(); const newUrl = window.location.pathname + (searchString ? '?' + searchString : ''); window.history.replaceState(null, '', newUrl); this.init(); } }, addPortlet(action) { const link = mw.util.addPortletLink(config.menu, '#', 'Headmaster', 'ca-hm', 'Interactively convert pseudo-headings'); if (link) { $(link).on('click', (e) => { e.preventDefault(); if (action === 'view') { this.redirectToEdit(); } else if (['edit', 'submit'].includes(action)) { this.init(); } }); } }, redirectToEdit() { window.location.href = mw.util.getUrl(mw.config.get("wgPageName"), { action: 'edit', headmaster: '1' }); }, silentScan() { new mw.Api().get({ action: 'query', prop: 'revisions', titles: mw.config.get('wgPageName'), rvprop: 'content', rvlimit: 1, formatversion: 2 }).done((data) => { const page = data.query.pages[0]; if (!page || !page.revisions) return; const wikitext = page.revisions[0].content; const tasks = this.analyzeText(wikitext); if (tasks.length > 0) { if (config.autoRun) { this.redirectToEdit(); } else { this.notifyDiscovery(tasks.length); } } }).fail((err) => { mw.log.warn("Headmaster: silentScan failed", err); }); }, notifyDiscovery(count) { const $msg = $('<span>').text(`Headmaster: Found ${count} issues. `).append($('<a>').text('Run conversion').css({ fontWeight: 'bold', cursor: 'pointer' }).on('click', () => this.redirectToEdit())); mw.notify($msg, { tag: 'hm-discovery', autoHide: false }); }, init() { if (!this.styleInjected) { mw.loader.addStyleTag(this.css); this.styleInjected = true; } const $textbox = $('#wpTextbox1'); const wikitext = $textbox.textSelection('getContents'); if (!wikitext) { mw.notify("Textbox is empty or not found.", { type: 'error' }); return; } const tasks = this.analyzeText(wikitext); if (tasks.length === 0) { mw.notify("No issues found."); return; } this.showUI(tasks, wikitext); }, analyzeText(wikitext) { const lines = wikitext.split("\n"); const tasks = []; let currentDepth = 1; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const headingMatch = line.match(/^(={2,6})\s*(.+?)\s*\1\s*$/); if (headingMatch) { currentDepth = headingMatch[1].length; const prevLine = i > 0 ? lines[i - 1].trim() : null; const isSmashed = prevLine !== null && prevLine !== ""; const isExtraSpace = i > 1 && lines[i - 1].trim() === "" && lines[i - 2].trim() === ""; if (isSmashed || isExtraSpace) { tasks.push({ index: i, type: 'WHITESPACE', original: lines[i], isSmashed: isSmashed, isExtraSpace: isExtraSpace }); } } else if (line.startsWith(';') && !line.startsWith(';;')) { const nextLine = lines[i + 1] || ""; if (!nextLine.trim().startsWith(':')) { const cleanText = line.substring(1).replace(/\s*:.*$/, "").trim(); const suggestedLevel = "=".repeat(Math.min(currentDepth + 1, 6)); tasks.push({ index: i, type: 'PSEUDO', original: line, cleanText: cleanText, context: nextLine.substring(0, 60).trim() + (nextLine.length > 60 ? "..." : ""), suggestedLevel: suggestedLevel }); } } } return tasks; }, showUI(tasks, originalWikitext) { $('#hm-panel').remove(); $('#editform').hide(); const visibleTasks = tasks.filter(t => t.type === 'PSEUDO'); const pseudoCount = visibleTasks.length; const whitespaceCount = tasks.filter(t => t.type === 'WHITESPACE').length; let statusMsg = ""; const s = (n) => n !== 1 ? 's' : ''; if (pseudoCount > 0) { statusMsg = `Found <strong>${pseudoCount}</strong> possible pseudo-heading${s(pseudoCount)}.`; if (whitespaceCount > 0) { statusMsg += ` Also fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } } else { statusMsg = `Did not find any pseudo-headings, but fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } const tableRows = pseudoCount > 0 ? visibleTasks.map((task, i) => ` <tr data-task-index="${task.index}"> <td><button class="hm-locate-btn" data-hm-i="${tasks.indexOf(task)}" title="Show in editor">Locate</button></td> <td class="hm-context"><code>${mw.html.escape(task.original)}</code></td> <td><label><input type="radio" name="hm-opt-${i}" value="keep"> Keep</label></td> <td><label><input type="radio" name="hm-opt-${i}" value="bold" checked> <b>Bold</b></label></td> <td><label><input type="radio" name="hm-opt-${i}" value="head"> Heading (${task.suggestedLevel.length})</label></td> <td class="hm-context">${mw.html.escape(task.context || "(end of page)")}</td> </tr> `).join("") : `<tr><td colspan="6" style="text-align:center; padding: 2em; color: #54595d; background: #f8f9fa;"> No interactive heading changes detected. Only background spacing normalization will be applied. </td></tr>`; const $panel = $(` <div id="hm-panel"> <h3 style="margin-top:0">Headmaster v${this.VERSION}: Review pseudo-headings</h3> <div class="hm-global-selector" ${pseudoCount === 0 ? 'style="display:none"' : ''}> <strong>Bulk action:</strong> <a href="#" id="hm-all-keep">Set all to Keep</a> | <a href="#" id="hm-all-bold">Set all to Bold</a> | <a href="#" id="hm-all-head">Set all to Heading</a> </div> <table id="hm-table"> <thead> <tr> <th>Find</th> <th>Source</th> <th colspan="3">Action</th> <th>Next line context</th> </tr> </thead> <tbody>${tableRows}</tbody> </table> <div id="hm-controls"> <span>${statusMsg}</span> <div> <button id="hm-cancel" class="mw-ui-button">Cancel</button> <button id="hm-apply" class="hm-btn-apply">Show changes (diff)</button> </div> </div> </div> `); $('#content').prepend($panel); window.scrollTo(0, 0); $('.hm-locate-btn').on('click', function() { $(document).off('mousedown.hmlocate'); const idx = $(this).data('hm-i'); const task = tasks[idx]; const lines = originalWikitext.split('\n'); let charOffset = 0; for (let j = 0; j < task.index; j++) { charOffset += lines[j].length + 1; } $('#hm-panel').hide(); $('#editform').show(); const $textbox = $('#wpTextbox1'); $textbox.textSelection('setSelection', { start: charOffset, end: charOffset + lines[task.index].length }); $textbox.textSelection('scrollToCaretPosition'); const locateNotify = mw.notify("Locating line " + (task.index + 1) + ". Click outside the editor to return.", { tag: 'hm-locate', type: 'success', autoHide: false }); setTimeout(() => { $(document).on('mousedown.hmlocate', function(e) { if (!$(e.target).closest('#editform, .mw-notification-area').length) { $(document).off('mousedown.hmlocate'); Promise.resolve(locateNotify).then(n => n.close()); $('#editform').hide(); $('#hm-panel').show(); } }); }, 200); }); $('#hm-all-keep').on('click', (e) => { e.preventDefault(); $('input[value="keep"]').prop('checked', true); }); $('#hm-all-bold').on('click', (e) => { e.preventDefault(); $('input[value="bold"]').prop('checked', true); }); $('#hm-all-head').on('click', (e) => { e.preventDefault(); $('input[value="head"]').prop('checked', true); }); $('#hm-cancel').on('click', () => { $('#hm-panel').remove(); $('#editform').show(); }); $('#hm-apply').on('click', () => this.applyChanges(tasks, originalWikitext)); }, getProcessedSummary(existingSummary, bolds, heads, spaces) { if (!config.enableSummary) return existingSummary; let mySummary = ""; let isDefault = false; if (config.customSummary) { mySummary = config.customSummary; } else { isDefault = true; if (bolds > 0 || heads > 0) { const detailsArr = []; if (bolds > 0) detailsArr.push(`${bolds} to bold`); if (heads > 0) detailsArr.push(`${heads} to heading`); const detailsStr = detailsArr.length > 0 ? `(${detailsArr.join(', ')}) ` : ""; mySummary = this.DefaultSummary.replace("{details}", detailsStr); if (spaces > 0) { mySummary += " Also handled [[MOS:BLANKLINE]]."; } } else if (spaces > 0) { mySummary = "Handling [[MOS:BLANKLINE]] using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]"; } } if (!mySummary) return existingSummary; const trimmedOld = existingSummary.trim(); if (!trimmedOld) return mySummary; if (/[.!?]$/.test(trimmedOld)) { return trimmedOld + " " + mySummary; } else { const finalSummary = isDefault ? mySummary.charAt(0).toLowerCase() + mySummary.slice(1) : mySummary; return trimmedOld + "; " + finalSummary; } }, applyChanges(tasks, wikitext) { try { let lines = wikitext.split("\n"); let bCount = 0, hCount = 0, wCount = 0; const pseudoTasks = tasks.filter(t => t.type === 'PSEUDO'); for (let i = pseudoTasks.length - 1; i >= 0; i--) { const task = pseudoTasks[i]; const idx = task.index; const action = $('#hm-table tbody tr').filter(`[data-task-index="${idx}"]`).find('input:checked').val(); if (action === 'bold') { let newValue = `'''${task.cleanText}'''`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; bCount++; } else if (action === 'head') { const h = task.suggestedLevel; let newValue = `${h} ${task.cleanText} ${h}`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; hCount++; } } const spaceTasks = tasks.filter(t => t.type === 'WHITESPACE'); for (let i = spaceTasks.length - 1; i >= 0; i--) { const task = spaceTasks[i]; const idx = task.index; if (task.isSmashed) { lines.splice(idx, 0, ""); } else if (task.isExtraSpace) { let backIdx = idx - 1; while (backIdx > 0 && lines[backIdx].trim() === "" && lines[backIdx - 1].trim() === "") { lines.splice(backIdx, 1); backIdx--; } } wCount++; } if (bCount + hCount + wCount === 0) { mw.notify("No changes selected."); $('#hm-panel').remove(); $('#editform').show(); return; } $('#wpTextbox1').textSelection('setContents', lines.join("\n")); const $summary = $('#wpSummary'); $summary.val(this.getProcessedSummary($summary.val(), bCount, hCount, wCount)); $('#hm-panel').remove(); $('#editform').show(); mw.notify(`Headmaster: Fixed ${wCount} spacing issue(s) and converted ${bCount + hCount} heading(s).`); $('html, body').animate({ scrollTop: 0 }, 'fast'); $('#wpDiff').click(); } catch (err) { $('#editform').show(); mw.notify("Headmaster error: " + err.message, { type: 'error' }); } } }; window.Headmaster = Headmaster; mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => { $(() => Headmaster.setup()); }); })(); //</nowiki> 474yiwgxhofsu5vectyyc3zcm1u7ad5 739854 739834 2026-04-30T08:41:20Z Sam Sailor 26820 Test 739854 javascript text/javascript //<nowiki> /* global ComradeLib, mw, $ */ // Quality suite (async function() { await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-lib.js&action=raw&ctype=text/javascript'); async function getTargetPage(api, name, type) { const titles = type === 'surname' ? [`List of people with the English surname ${name}`, `List of people with surname ${name}`, `${name} (surname)`, `${name} (name)`, name] : [`List of people with given name ${name}`, `List of people named ${name}`, `${name} (given name)`, `${name} (name)`, name]; const res = await api.get({ action: 'query', titles: titles.join('|'), formatversion: 2 }); if (!res.query || !res.query.pages) return null; return titles.find(t => { const pg = res.query.pages.find(p => p.title === t); return pg && !pg.missing; }); } async function processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean) { const { givens, surnames } = await fetchWikidataNameLabels(entity); const nameParts = tClean.split(' '); const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const primarySurname = hasFamilyNameHatnote ? nameParts[0] : (getSmartSortKey(tClean, entity).split(',')[0] || nameParts[nameParts.length - 1]); for (const name of givens) { if (name.toLowerCase() === primarySurname.toLowerCase()) continue; const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'given'); if (target) await verifyAndNudge(api, name, 'given name', currentTitle, hasShortDesc, isOrphan, target); } const finalSurnames = surnames.length > 0 ? surnames : (nameParts.length >= 2 ? [nameParts[nameParts.length - 1]] : []); for (const name of finalSurnames) { const nameInTitle = new RegExp('\\b' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i').test(tClean); if (!nameInTitle) continue; const target = await getTargetPage(api, name, 'surname'); if (target) await verifyAndNudge(api, name, 'surname', currentTitle, hasShortDesc, isOrphan, target); } } async function verifyAndNudge(api, name, type, currentTitle, hasShortDesc, isOrphan, foundTitle) { const res = await api.get({ action: 'query', prop: 'revisions', titles: foundTitle, rvprop: 'content', formatversion: 2 }); const page = res.query.pages[0]; if (!page || page.missing || !page.revisions) return; const text = page.revisions[0].content; const isNameMatch = /\{\{(Surname|Hndis|Given[ _]name|Forename|Givenname)/i.test(text); const isNameDab = /\{\{(Disambiguation|Dab|Disambig)(?:[^}]*\|(surname|surnames|given[ _]name|given[ _]names)|(?:\s*\}\}))/i.test(text); const isNamePageTitle = foundTitle.includes('List of people with'); if (!isNameMatch && !isNameDab && !isNamePageTitle) return; const escapedTitle = currentTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/ /g, '[ _]'); const alreadyListed = new RegExp('(\\[\\[|\\{\\{anbl\\|[ _]*|\\|)' + escapedTitle, 'i').test(text); if (alreadyListed) return; const $container = $('<div>').addClass('comrade-container').addClass(isOrphan ? 'comrade-nudge' : 'comrade-info'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text(isOrphan ? 'Comrade nudge:' : 'Informative:')); const $content = $('<div>').addClass('comrade-content'); $content.append($('<span>').append(`${type.charAt(0).toUpperCase() + type.slice(1)} not listed at `, $('<a>').attr({ href: mw.util.getUrl(foundTitle), target: '_blank' }).text(foundTitle), "; 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('Consider adding ', $('<code>').text(snippet)); const $copyBtn = $('<button>').text('Copy & insert').on('click', function() { navigator.clipboard.writeText('\n' + snippet).then(() => { $(this).text('Copied!').prop('disabled', true); const editUrl = mw.util.getUrl(foundTitle, { action: 'edit', summary: 'Adding * {{[[Template:Annotated biography link|anbl]]|[[' + currentTitle + ']]}}' }); window.open(editUrl, '_blank'); }); }); $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); ComradeLib.appendDismiss($container); } async function checkNamingPrecision(currentTitle, isHuman) { const disambigMatch = currentTitle.match(/^(.+?)(?:, | \()/); if (isHuman || !disambigMatch) return; const baseTitle = disambigMatch[1].trim(); const api = new mw.Api(); try { const baseRes = await api.get({ action: 'query', titles: baseTitle, redirects: 1, formatversion: 2 }); const page = baseRes.query.pages[0]; const baseExists = !page.missing; let baseRedirectsToSelf = false; if (baseRes.query.redirects) { baseRedirectsToSelf = baseRes.query.redirects.some(r => r.to === currentTitle); } const searchRes = await api.get({ action: 'query', list: 'allpages', apprefix: baseTitle, aplimit: 50, formatversion: 2 }); const competitors = searchRes.query.allpages.filter(p => { const t = p.title; return t !== currentTitle && t !== baseTitle && (t.startsWith(baseTitle + ', ') || t.startsWith(baseTitle + ' (')); }); if (!baseExists || baseRedirectsToSelf) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').addClass('comrade-content'); if (competitors.length === 0) { $header.append($('<strong>').text('Precision:')); const reason = baseRedirectsToSelf ? "already redirects here" : "is free"; $content.append($('<span>').append(`The base name `, $('<code>').text(baseTitle), ` ${reason}. Consider moving this article.`)); const moveUrl = mw.util.getUrl('Special:MovePage/' + currentTitle, { wpNewTitle: baseTitle, wpReason: 'Unnecessary disambiguation; base name is free/redirects here per [[WP:PRECISION]]' }); $content.append($('<button>').text(`Move to ${baseTitle}`).on('click', () => window.open(moveUrl, '_blank'))); } else { $header.append($('<strong>').text('Observation:')); const state = baseRedirectsToSelf ? 'redirect to this page' : 'redlink'; $content.append($('<span>').append(`Base name `, $('<code>').text(baseTitle), ` has ${competitors.length} competitors, but it is currently a ${state}.`)); $content.append($('<span>').text('Consider if a disambiguation page is needed.')); } $container.append($header, $content); ComradeLib.appendDismiss($container); } } catch (e) { console.warn("Comrade: Precision check failed", e); } } async function checkHumanSort(wikitext, entity, currentTitle, tClean, refName) { const dsMatch = wikitext.match(/\{\{(?:DEFAULTSORT|Defaultsort):([^}]+)\}\}/i); const { givens } = await fetchWikidataNameLabels(entity); if (dsMatch) { const currentDS = dsMatch[1].trim(); if (currentDS.includes(',')) { let [lastPart, firstPart] = currentDS.split(',').map(s => s.trim()); if (firstPart.toLowerCase().endsWith(lastPart.toLowerCase())) { let fixedFirst = firstPart.substring(0, firstPart.length - lastPart.length).trim(); let suggestion = `${fixNameCasing(lastPart, refName)}, ${fixNameCasing(fixedFirst, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, "Redundant surname in given name field."); } else if (givens.length > 0 && lastPart.split(' ').length > 1) { const parts = lastPart.split(' '); const misplacedGiven = parts.find(part => givens.some(gn => gn.toLowerCase() === part.toLowerCase())); if (misplacedGiven) { const newSurname = parts.filter(p => p !== misplacedGiven).join(' '); let suggestion = `${fixNameCasing(newSurname, refName)}, ${fixNameCasing(misplacedGiven + ' ' + firstPart, refName)}`; if (suggestion !== currentDS) renderSortNudge(suggestion, `Wikidata suggests "${misplacedGiven}" is a given name.`); } } } } else { const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const nameParts = tClean.split(' '); const targetSortName = hasFamilyNameHatnote ? `${nameParts[0]}, ${nameParts.slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { const $dsMissing = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Add: {{DEFAULTSORT:${targetSortName}}}`).on('click', () => performSortCorrection(targetSortName, "add")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` Tag is missing. `, $btn)); $dsMissing.append($header); ComradeLib.appendDismiss($dsMissing); } } } async function fetchWikidataEntity(qid) { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: qid, props: 'claims|labels', languages: 'en', format: 'json', origin: '*' }); try { const wikiData = await fetch(url).then(r => r.json()); return wikiData.entities[qid]; } catch (e) { return null; } } function getSmartSortKey(fullName, entity) { const countryIds = entity.claims?.P27?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const locationIds = entity.claims?.P17?.map(c => c.mainsnak?.datavalue?.value?.id) || []; const placeIds = [...(entity.claims?.P19 || []), ...(entity.claims?.P119 || [])].map(c => c.mainsnak?.datavalue?.value?.id).filter(Boolean); const allRelevantIds = [...countryIds, ...locationIds, ...placeIds]; const birthYear = parseInt(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.match(/[+-](\d{4})/)?.[1]) || null; if (fullName.startsWith("Saint ")) return fullName.replace("Saint ", ""); const arabicParticles = /\b(Abu|Abd|Abdel|Abdul|ben|bin|bint)\b/i; if (fullName.match(arabicParticles)) { if (birthYear && birthYear > 1900) { const parts = fullName.split(' '); const binIndex = parts.findIndex(p => p.toLowerCase() === 'bin' || p.toLowerCase() === 'ben'); if (binIndex > 0) return parts.slice(binIndex).join(' ') + ', ' + parts.slice(0, binIndex).join(' '); } else { return fullName; } } const eastAsianQids = ["Q148", "Q865", "Q884", "Q424", "Q17", "Q881"]; if (allRelevantIds.some(id => eastAsianQids.includes(id))) { if (allRelevantIds.includes("Q17") && birthYear && birthYear > 1885) return westernSort(fullName); const parts = fullName.split(' '); if (parts.length > 1) return `${parts[0]}, ${parts.slice(1).join(' ')}`; } return westernSort(fullName); } function westernSort(name) { let cleanName = name; if (name.startsWith("O'")) cleanName = name.replace("O'", "O"); const parts = cleanName.split(' '); if (parts.length < 2) return cleanName; const surname = parts.pop(); if (["Jr.", "Jr", "III", "II", "Sr.", "Sr"].includes(surname)) { const actualSurname = parts.pop(); return `${actualSurname}, ${parts.join(' ')} ${surname}`; } return `${surname}, ${parts.join(' ')}`; } async function performSortCorrection(newName, type) { const api = new mw.Api(); const title = mw.config.get("wgPageName"); try { const data = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = data.query.pages[0].revisions[0].content; const dsRegex = /\{\{\s*(?:DEFAULTSORT|Defaultsort)\s*:[^}]+\}\}/i; const newTag = `{{DEFAULTSORT:${newName}}}`; if (dsRegex.test(content)) { content = content.replace(dsRegex, newTag); } else { const catRegex = /\[\[Category:/i; content = catRegex.test(content) ? content.replace(catRegex, `${newTag}\n[[Category:`) : content.trim() + `\n\n${newTag}`; } await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Setting DEFAULTSORT to ${newName}`, nocreate: true }); mw.notify(`DEFAULTSORT ${type === "add" ? "added" : "corrected"} to ${newName}!`, { title: 'Comrade Success', type: 'success' }); setTimeout(() => location.reload(), 700); } catch (err) { mw.notify('Failed to save DEFAULTSORT.', { type: 'error' }); } } function renderSortNudge(target, reason) { const $dsNudge = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $btn = $('<button>').text(`Correct to: ${target}`).on('click', () => performSortCorrection(target, "format")); $header.append($('<div>').append($('<strong>').text('DEFAULTSORT:'), ` ${reason} `, $btn)); $dsNudge.append($header); ComradeLib.appendDismiss($dsNudge); } async function fetchWikidataNameLabels(entity) { const familyIds = entity.claims?.P734?.map(c => c.mainsnak.datavalue.value.id) || []; const givenIds = entity.claims?.P735?.map(c => c.mainsnak.datavalue.value.id) || []; const allNameIds = [...new Set([...familyIds, ...givenIds])]; if (allNameIds.length === 0) return { givens: [], surnames: [] }; const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetentities', ids: allNameIds.join('|'), props: 'labels', languages: 'en', format: 'json', origin: '*' }); const nameData = await fetch(url).then(r => r.json()); return { givens: givenIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean), surnames: familyIds.map(id => nameData.entities[id]?.labels?.en?.value).filter(Boolean) }; } function fixNameCasing(part, referenceName) { return part.split(' ').map(word => { const match = referenceName.match(new RegExp(`\\b${word}\\b`, 'i')); if (match) return match[0]; return referenceName.split(' ').find(tWord => word.toLowerCase().startsWith(tWord.toLowerCase()) || tWord.toLowerCase().startsWith(word.toLowerCase())) || word; }).join(' '); } async function init() { const api = new mw.Api(); const ns = mw.config.get("wgNamespaceNumber"); const action = mw.config.get("wgAction"); if (mw.config.get("wgDBname") !== "enwiki" || action !== "view") return; if (ns === 14) { const $btn = $('<button>').attr('id', 'comrade-audit-btn').text('Audit category quality').css({ 'margin-bottom': '10px', 'padding': '5px 10px', 'cursor': 'pointer' }).on('click', ComradeLib.performCategoryAudit); $('.mw-category-generated').prepend($btn); return; } if (ns !== 0) return; const qid = mw.config.get("wgWikibaseItemId"); const currentTitle = mw.config.get("wgTitle"); 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; // Ligature/ASCII-only/diacritic and domain const norms = ComradeLib.getNormalizedTitles(currentTitle); if (norms.ascii) { let label = "ASCII-only"; let category = "R from ASCII-only"; if (/[æÆœŒijIJßẞ]/.test(currentTitle)) { label = "Ligature"; category = "R to ligature"; } else if (currentTitle.normalize("NFD").match(/[\u0300-\u036f]/)) { label = "Diacritic"; category = "R to diacritic"; } await ComradeLib.checkAndRenderRedirect(norms.ascii, currentTitle, category, label); } await ComradeLib.checkDomainRedirect(qid, currentTitle); // Orphan logic 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 $btn = $('<button>').text('Remove orphan tag').on('click', async () => await ComradeLib.performDeorphan()); const $statusText = $('<span>').append($('<strong>').text('Status:'), ` Article has ${backLinkCount} backlink(s).`, $btn); $container.append($('<div>').addClass('comrade-header').append($statusText)); ComradeLib.appendDismiss($container); } // Wikidata/human logic let entity = qid ? await fetchWikidataEntity(qid) : null; let isHuman = entity?.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5"); await checkNamingPrecision(currentTitle, isHuman); if (isHuman && entity) { const tClean = currentTitle.replace(/\s*\(.*?\)\s*/g, '').trim(); const refName = entity.labels?.en?.value || currentTitle; const hasFamilyNameHatnote = /\{\{family[ _]name[ _]hatnote/i.test(wikitext); const hasShortDesc = /\{\{\s*[Ss]hort description/i.test(wikitext); const isOrphan = isOrphanTagged && backLinkCount < 1; await ComradeLib.checkShortDescDates(wikitext, entity); // Check DEFAULTSORT await checkHumanSort(wikitext, entity, currentTitle, tClean, refName); const longName = ComradeLib.getLongNameFromWikitext(wikitext); if (longName && longName.length > tClean.length) { let sortKey = hasFamilyNameHatnote ? `${longName.split(' ')[0]}, ${longName.split(' ').slice(1).join(' ')}` : getSmartSortKey(longName, entity); const rLongWikitext = `#REDIRECT [[${currentTitle}]]\n\n{{Redirect category shell|\n{{R from long name}}\n}}\n{{DEFAULTSORT:${sortKey}}}`; await ComradeLib.checkAndRenderRedirect(longName, currentTitle, null, "Long name", rLongWikitext); } // Sort name R const targetSortName = hasFamilyNameHatnote ? `${tClean.split(' ')[0]}, ${tClean.split(' ').slice(1).join(' ')}` : getSmartSortKey(tClean, entity); if (targetSortName && targetSortName.toLowerCase() !== currentTitle.toLowerCase()) { let targetPage = currentTitle.includes(' (') ? currentTitle.split(' (')[0] : currentTitle; let rCat = targetPage !== currentTitle ? "R from ambiguous sort name" : "R from sort name"; if (targetSortName.includes(',')) { const p = targetSortName.split(','); rCat += `|${p[0].trim().charAt(0).toUpperCase()}|${p[1].trim().charAt(0).toUpperCase()}`; } await ComradeLib.checkAndRenderRedirect(targetSortName, targetPage, rCat, "Sort name"); } // Name lists await processHumanNameLists(api, entity, wikitext, currentTitle, hasShortDesc, isOrphan, tClean); } // Quality/ORES const $oresElement = $('.article_quality'); if ($oresElement.length && $oresElement.text().trim().length > 0) { ComradeLib.checkQualityConsistency(); } else { const qualityTarget = document.querySelector('#mw-content-text'); if (qualityTarget) { const observer = new MutationObserver((mutations, obs) => { const $oresElement = $('.article_quality'); if ($oresElement.length && $oresElement.text().trim().length > 0) { obs.disconnect(); ComradeLib.checkQualityConsistency(); } }); observer.observe(qualityTarget, { childList: true, subtree: true }); setTimeout(() => observer.disconnect(), 10000); } } } catch (err) { console.error("Comrade error:", err); } } init(); })(); (function() { if (window.Headmaster) return; const VERSION = "0.9.2.1"; const defaultNS = [0, 118]; const userNS = window.headmaster_ns || []; const config = { menu: window.headmaster_menu || 'p-cactions', enableSummary: window.headmaster_do_summary !== false, customSummary: window.headmaster_summary || "", allowedNamespaces: Array.from(new Set([...defaultNS, ...userNS])), autoScan: window.headmaster_auto_scan !== false, autoRun: window.headmaster_auto_run === false }; const Headmaster = { VERSION: VERSION, DefaultSummary: "Converting [[MOS:PSEUDOHEAD|pseudo-headings]] {details}using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]. Semicolon markup is reserved for [[MOS:DLIST|description lists]].", styleInjected: false, css: ` #hm-panel { clear: both; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; margin-bottom: 1.5em; border-radius: 2px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } #hm-table { width: 100%; border-collapse: collapse; background: white; margin-top: 10px; } #hm-table th { background: #f2f2f2; border: 1px solid #a2a9b1; padding: 8px; text-align: left; } #hm-table td { border: 1px solid #a2a9b1; padding: 8px; vertical-align: middle; } .hm-context { font-family: 'Courier New', monospace; font-size: 0.9em; color: #54595d; background: #fdfdfd; } #hm-controls { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; } #hm-controls button { margin-left: 10px; padding: 6px 16px; cursor: pointer; border-radius: 2px; } .hm-btn-apply { background: #36c; color: white; border: 1px solid #36c; font-weight: bold; } .hm-btn-apply:hover { background: #447ff5; } .hm-global-selector { font-size: 0.9em; color: #202122; background: #eaecf0; padding: 8px; border-radius: 2px; border: 1px solid #a2a9b1; } .hm-global-selector a { font-weight: bold; text-decoration: none; color: #36c; } .hm-global-selector a:hover { text-decoration: underline; } .hm-locate-btn { cursor: pointer; color: #36c; border: 1px solid #a2a9b1; background: #f8f9fa; padding: 2px 6px; font-size: 0.85em; border-radius: 2px; } .hm-locate-btn:hover { background: #fff; border-color: #36c; } `, setup() { if (!config.allowedNamespaces.includes(mw.config.get("wgNamespaceNumber"))) return; const action = mw.config.get("wgAction"); this.addPortlet(action); if (action === 'view' && (config.autoScan || config.autoRun)) { this.silentScan(); } if (mw.util.getParamValue('headmaster') === '1' && ['edit', 'submit'].includes(action)) { const params = new URLSearchParams(window.location.search); params.delete('headmaster'); const searchString = params.toString(); const newUrl = window.location.pathname + (searchString ? '?' + searchString : ''); window.history.replaceState(null, '', newUrl); this.init(); } }, addPortlet(action) { const link = mw.util.addPortletLink(config.menu, '#', 'Headmaster', 'ca-hm', 'Interactively convert pseudo-headings'); if (link) { $(link).on('click', (e) => { e.preventDefault(); if (action === 'view') { this.redirectToEdit(); } else if (['edit', 'submit'].includes(action)) { this.init(); } }); } }, redirectToEdit() { window.location.href = mw.util.getUrl(mw.config.get("wgPageName"), { action: 'edit', headmaster: '1' }); }, silentScan() { new mw.Api().get({ action: 'query', prop: 'revisions', titles: mw.config.get('wgPageName'), rvprop: 'content', rvlimit: 1, formatversion: 2 }).done((data) => { const page = data.query.pages[0]; if (!page || !page.revisions) return; const wikitext = page.revisions[0].content; const tasks = this.analyzeText(wikitext); if (tasks.length > 0) { if (config.autoRun) { this.redirectToEdit(); } else { this.notifyDiscovery(tasks.length); } } }).fail((err) => { mw.log.warn("Headmaster: silentScan failed", err); }); }, notifyDiscovery(count) { const $msg = $('<span>').text(`Headmaster: Found ${count} issues. `).append($('<a>').text('Run conversion').css({ fontWeight: 'bold', cursor: 'pointer' }).on('click', () => this.redirectToEdit())); mw.notify($msg, { tag: 'hm-discovery', autoHide: false }); }, init() { if (!this.styleInjected) { mw.loader.addStyleTag(this.css); this.styleInjected = true; } const $textbox = $('#wpTextbox1'); const wikitext = $textbox.textSelection('getContents'); if (!wikitext) { mw.notify("Textbox is empty or not found.", { type: 'error' }); return; } const tasks = this.analyzeText(wikitext); if (tasks.length === 0) { mw.notify("No issues found."); return; } this.showUI(tasks, wikitext); }, analyzeText(wikitext) { const lines = wikitext.split("\n"); const tasks = []; let currentDepth = 1; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const headingMatch = line.match(/^(={2,6})\s*(.+?)\s*\1\s*$/); if (headingMatch) { currentDepth = headingMatch[1].length; const prevLine = i > 0 ? lines[i - 1].trim() : null; const isSmashed = prevLine !== null && prevLine !== ""; const isExtraSpace = i > 1 && lines[i - 1].trim() === "" && lines[i - 2].trim() === ""; if (isSmashed || isExtraSpace) { tasks.push({ index: i, type: 'WHITESPACE', original: lines[i], isSmashed: isSmashed, isExtraSpace: isExtraSpace }); } } else if (line.startsWith(';') && !line.startsWith(';;')) { const nextLine = lines[i + 1] || ""; if (!nextLine.trim().startsWith(':')) { const cleanText = line.substring(1).replace(/\s*:.*$/, "").trim(); const suggestedLevel = "=".repeat(Math.min(currentDepth + 1, 6)); tasks.push({ index: i, type: 'PSEUDO', original: line, cleanText: cleanText, context: nextLine.substring(0, 60).trim() + (nextLine.length > 60 ? "..." : ""), suggestedLevel: suggestedLevel }); } } } return tasks; }, showUI(tasks, originalWikitext) { $('#hm-panel').remove(); $('#editform').hide(); const visibleTasks = tasks.filter(t => t.type === 'PSEUDO'); const pseudoCount = visibleTasks.length; const whitespaceCount = tasks.filter(t => t.type === 'WHITESPACE').length; let statusMsg = ""; const s = (n) => n !== 1 ? 's' : ''; if (pseudoCount > 0) { statusMsg = `Found <strong>${pseudoCount}</strong> possible pseudo-heading${s(pseudoCount)}.`; if (whitespaceCount > 0) { statusMsg += ` Also fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } } else { statusMsg = `Did not find any pseudo-headings, but fixing <strong>${whitespaceCount}</strong> spacing issue${s(whitespaceCount)} quietly.`; } const tableRows = pseudoCount > 0 ? visibleTasks.map((task, i) => ` <tr data-task-index="${task.index}"> <td><button class="hm-locate-btn" data-hm-i="${tasks.indexOf(task)}" title="Show in editor">Locate</button></td> <td class="hm-context"><code>${mw.html.escape(task.original)}</code></td> <td><label><input type="radio" name="hm-opt-${i}" value="keep"> Keep</label></td> <td><label><input type="radio" name="hm-opt-${i}" value="bold" checked> <b>Bold</b></label></td> <td><label><input type="radio" name="hm-opt-${i}" value="head"> Heading (${task.suggestedLevel.length})</label></td> <td class="hm-context">${mw.html.escape(task.context || "(end of page)")}</td> </tr> `).join("") : `<tr><td colspan="6" style="text-align:center; padding: 2em; color: #54595d; background: #f8f9fa;"> No interactive heading changes detected. Only background spacing normalization will be applied. </td></tr>`; const $panel = $(` <div id="hm-panel"> <h3 style="margin-top:0">Headmaster v${this.VERSION}: Review pseudo-headings</h3> <div class="hm-global-selector" ${pseudoCount === 0 ? 'style="display:none"' : ''}> <strong>Bulk action:</strong> <a href="#" id="hm-all-keep">Set all to Keep</a> | <a href="#" id="hm-all-bold">Set all to Bold</a> | <a href="#" id="hm-all-head">Set all to Heading</a> </div> <table id="hm-table"> <thead> <tr> <th>Find</th> <th>Source</th> <th colspan="3">Action</th> <th>Next line context</th> </tr> </thead> <tbody>${tableRows}</tbody> </table> <div id="hm-controls"> <span>${statusMsg}</span> <div> <button id="hm-cancel" class="mw-ui-button">Cancel</button> <button id="hm-apply" class="hm-btn-apply">Show changes (diff)</button> </div> </div> </div> `); $('#content').prepend($panel); window.scrollTo(0, 0); $('.hm-locate-btn').on('click', function() { $(document).off('mousedown.hmlocate'); const idx = $(this).data('hm-i'); const task = tasks[idx]; const lines = originalWikitext.split('\n'); let charOffset = 0; for (let j = 0; j < task.index; j++) { charOffset += lines[j].length + 1; } $('#hm-panel').hide(); $('#editform').show(); const $textbox = $('#wpTextbox1'); $textbox.textSelection('setSelection', { start: charOffset, end: charOffset + lines[task.index].length }); $textbox.textSelection('scrollToCaretPosition'); const locateNotify = mw.notify("Locating line " + (task.index + 1) + ". Click outside the editor to return.", { tag: 'hm-locate', type: 'success', autoHide: false }); setTimeout(() => { $(document).on('mousedown.hmlocate', function(e) { if (!$(e.target).closest('#editform, .mw-notification-area').length) { $(document).off('mousedown.hmlocate'); Promise.resolve(locateNotify).then(n => n.close()); $('#editform').hide(); $('#hm-panel').show(); } }); }, 200); }); $('#hm-all-keep').on('click', (e) => { e.preventDefault(); $('input[value="keep"]').prop('checked', true); }); $('#hm-all-bold').on('click', (e) => { e.preventDefault(); $('input[value="bold"]').prop('checked', true); }); $('#hm-all-head').on('click', (e) => { e.preventDefault(); $('input[value="head"]').prop('checked', true); }); $('#hm-cancel').on('click', () => { $('#hm-panel').remove(); $('#editform').show(); }); $('#hm-apply').on('click', () => this.applyChanges(tasks, originalWikitext)); }, getProcessedSummary(existingSummary, bolds, heads, spaces) { if (!config.enableSummary) return existingSummary; let mySummary = ""; let isDefault = false; if (config.customSummary) { mySummary = config.customSummary; } else { isDefault = true; if (bolds > 0 || heads > 0) { const detailsArr = []; if (bolds > 0) detailsArr.push(`${bolds} to bold`); if (heads > 0) detailsArr.push(`${heads} to heading`); const detailsStr = detailsArr.length > 0 ? `(${detailsArr.join(', ')}) ` : ""; mySummary = this.DefaultSummary.replace("{details}", detailsStr); if (spaces > 0) { mySummary += " Also handled [[MOS:BLANKLINE]]."; } } else if (spaces > 0) { mySummary = "Handling [[MOS:BLANKLINE]] using [[User:Sam_Sailor/Scripts/Headmaster.js|Headmaster]]"; } } if (!mySummary) return existingSummary; const trimmedOld = existingSummary.trim(); if (!trimmedOld) return mySummary; if (/[.!?]$/.test(trimmedOld)) { return trimmedOld + " " + mySummary; } else { const finalSummary = isDefault ? mySummary.charAt(0).toLowerCase() + mySummary.slice(1) : mySummary; return trimmedOld + "; " + finalSummary; } }, applyChanges(tasks, wikitext) { try { let lines = wikitext.split("\n"); let bCount = 0, hCount = 0, wCount = 0; const pseudoTasks = tasks.filter(t => t.type === 'PSEUDO'); for (let i = pseudoTasks.length - 1; i >= 0; i--) { const task = pseudoTasks[i]; const idx = task.index; const action = $('#hm-table tbody tr').filter(`[data-task-index="${idx}"]`).find('input:checked').val(); if (action === 'bold') { let newValue = `'''${task.cleanText}'''`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; bCount++; } else if (action === 'head') { const h = task.suggestedLevel; let newValue = `${h} ${task.cleanText} ${h}`; if (idx > 0 && lines[idx - 1].trim() !== "") { newValue = "\n" + newValue; } lines[idx] = newValue; hCount++; } } const spaceTasks = tasks.filter(t => t.type === 'WHITESPACE'); for (let i = spaceTasks.length - 1; i >= 0; i--) { const task = spaceTasks[i]; const idx = task.index; if (task.isSmashed) { lines.splice(idx, 0, ""); } else if (task.isExtraSpace) { let backIdx = idx - 1; while (backIdx > 0 && lines[backIdx].trim() === "" && lines[backIdx - 1].trim() === "") { lines.splice(backIdx, 1); backIdx--; } } wCount++; } if (bCount + hCount + wCount === 0) { mw.notify("No changes selected."); $('#hm-panel').remove(); $('#editform').show(); return; } $('#wpTextbox1').textSelection('setContents', lines.join("\n")); const $summary = $('#wpSummary'); $summary.val(this.getProcessedSummary($summary.val(), bCount, hCount, wCount)); $('#hm-panel').remove(); $('#editform').show(); mw.notify(`Headmaster: Fixed ${wCount} spacing issue(s) and converted ${bCount + hCount} heading(s).`); $('html, body').animate({ scrollTop: 0 }, 'fast'); $('#wpDiff').click(); } catch (err) { $('#editform').show(); mw.notify("Headmaster error: " + err.message, { type: 'error' }); } } }; window.Headmaster = Headmaster; mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => { $(() => Headmaster.setup()); }); })(); //</nowiki> ptm0wyukd4jizr4bfyn6qky3tvz2hae Wikipedia:Administrators' noticeboard 4 103050 739797 620861 2026-04-29T20:24:32Z ~2026-26083-05 73757 /* حريم. بينيكوا بعض */ new section 739797 wikitext text/x-wiki __NEWSECTIONLINK__ {{short description|Page for requests and notifications to non-specific administrators}} <noinclude><!-- Inside the noinclude, because this page is transcluded. -->{{Wikipedia:Administrators' noticeboard/Header}}{{Active editnotice}}</noinclude> {{User:MiszaBot/config | algo = old(6d) | counter = 307 | archive = Wikipedia:Administrators' noticeboard/Archive%(counter)d | maxarchivesize = 700K | archiveheader = {{Administrators' noticeboard navbox all}} | minthreadstoarchive = 1 | minthreadsleft = 3 }} <!-- {{User:ClueBot III/ArchiveThis |header={{Administrators' noticeboard navbox all}} |archiveprefix=Wikipedia:Administrators' noticeboard/Archive |format=%%i |age=48 |index=no |numberstart=255 |minkeepthreads= 4 |maxarchsize= 700000 }} --> {{User:HBC Archive Indexerbot/OptIn|target=Wikipedia:Administrators' noticeboard/Archive index|mask=Wikipedia:Administrators' noticeboard/Archive<#>|leading_zeros=0|indexhere=no}}<!-- ---------------------------------------------------------- New entries go down at the *BOTTOM* of the page, not here. ---------------------------------------------------------- --><noinclude>{{TOC limit|3}}{{Wikipedia:Administrators' noticeboard/Requests for closure}} == Pages recently put under [[WP:ECP|extended-confirmed protection]] == {{collapse top|Report|expand=true}} {{User:MusikBot/ECPMonitor/Report}} {{collapse bottom}} ==Thousands of Portals== The purpose of this posting is to discuss [[WP:Portal|portals]], hundreds of portals. There is already discussion at [[WP:VPR|Village pump (Proposals)]] (see [[WP:Village pump (proposals)#Hiatus on mass creation of Portals]]) to stop the creation of large numbers of portals by [[User:The Transhumanist]], and the consensus is going strongly in favor of a hiatus, and there have been no new portals created since 22 February, but there has been no agreement to stop the creation of portals. The discussion at VPR appears to have slowed down, with a very clear consensus for some sort of hiatus, although it is not clear whether everyone agrees that the consensus is to stop the semi-automated creation of portals, or to stop the semi-automated creation of portals by TTH, or to stop all creation of portals by TTH (since there seems to be disagreement on what is semi-automated creation). Some editors have suggested that these portals are the equivalent of redirects by Neelix that warrant mass destruction. Anyway, proposals at VPR are just that, proposals. I am bringing the discussion here. Perhaps I don’t understand, but [[User:The Transhumanist]] appears to be saying that we need to use portals as an experiment in navigation and in innovation. I am not sure that I understand whether, by experiment, they mean testing, a new initiative, or what, but I am not sure that I understand what is being innovated, or why it requires hundreds or thousands of portals. [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 02:51, 1 March 2019 (UTC) : Note that the mass hiatus wasn't on me per se, but applicable in general. It applies to everyone. It's so that nobody mass creates portals for the time being. That includes me. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 05:53, 1 March 2019 (UTC) ====An Example and Some Comments==== One of the portals that has been proposed by [[User:Legacypac]] for deletion is [[Portal:English language]]. A look at it, with its error messages, is sadly informative. It was one of Wikipedia's earliest portals, preceding the involvement of the current portal team of TTH and a few other editors. However, the current portal team has made breaking changes to [[Portal:English language]], apparently in order to attempt to improve the maintenance of portals. They apparently don't know how to keep our existing portals working, so what business do they have creating thousands of additional portals? We are told that the new portals are maintenance-free or nearly maintenance-free, but have the new portals been created at the cost of breaking existing portals? [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 03:39, 2 March 2019 (UTC) Also, TTH says, above, that there is a hiatus that applies to everyone so that nobody mass creates portals for the time being. What is meant by mass creation, as opposed to individual creation? Are they agreeing not to create any portals for the time being? How long a time? Will they defer the creation of any new portals until (and unless) there is a consensus arrived at the criteria for the creation and maintenance of new portals? [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 03:39, 2 March 2019 (UTC) Also, if any editor wishes to propose mass deletion of portals, similar to Neely redirects, that can be Proposal 3 (or 4). [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 03:39, 2 March 2019 (UTC) :Further investigation found 2 of the 8 portals linked off the top of the Mainpage had similar Red Script Errors where content should be. [[User:Moxy]] has now reverted these to pre-automation status. A lot of effort goes into keeping content linked from the Mainpage error free, yet this little Portal Project group replaced featured article quality portals with automated junk. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 03:46, 2 March 2019 (UTC) :See [[WP:AADD#Surmountable problems]]. "Something broke but could be fixed" isn't a deletion rationale. Much less a deletion rationale for {{em|different}} pages; that's the [[guilt by association]] fallacy. Note also the {{lang|la|[[ad hominem]]}} fallacy in there too, making it about specific people and getting back at them and suggesting they're too incompetent to create a new page, etc., instead of the argument focusing on content and our systems of presenting and navigating it. Tsk tsk. <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 17:11, 14 March 2019 (UTC) {{clear}} ===Proposal 1: Interim Topic-Ban on New Portals === {{atop|There is a rough consensus to formalize a moratorium on creation of new portals, as proposed. There is some prominent opposition rooted in the notion that it is not necessary to formally sanction an editor who has already self-imposed the sanction, and this is a reasonable point. Other than that, however, there is little sentiment that the behavior is not problematic and worthy of preventative measures, voluntary or otherwise. So, the worst case scenario presented by the opposition is that a user is formally barred from repeating a specific action that they would not be repeating otherwise. This worst case scenario is harmless, both to the project, and the editor, with the only difference being that the preventative measure that everyone seems to agree on is binding, as opposed to voluntary and non-binding. Evidence has also been presented that, contrary to the voluntary moratorium, TTH ''continues'' to work to expand the portal space, even as of today, and has not engaged in anything that would indicate that they're actually working to resolve the problem they created in good faith. This sort of thing seems to damage the case being made that a TBAN would be completely pointless. Additionally, there is equally prominent support that feels this sanction is an ''underreaction'', and that much stronger sanctions, up to a full site-ban, are warranted. Will notify and log appropriately. Regards, [[User:Swarm|<span style="color:Green">'''~Swarm~'''</span>]] [[User talk:Swarm|<span style="color:DarkViolet">'''{talk}'''</span>]] 04:58, 17 March 2019 (UTC)}} I propose a [[WP:TBAN|topic-ban]] on the creation of portals by [[User:The Transhumanist]] for three months, to provide time for the development of new guidelines on portals, to provide time to dispose of some of the portals at [[WP:MFD|MFD]], and to provide time to consider whether it is necessary to mass-destroy portals. [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 02:51, 1 March 2019 (UTC) *'''Support''' as proposer. [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 03:02, 1 March 2019 (UTC) *'''Support''' the true number of portal creations by this user appears to be around 3500 portals since July 2018 (claims this here [https://en.wikipedia.org/wiki/User_talk:CASSIOPEIA/Archive_17#Creating_portals]). There were less than 1700 portals prior. On their talk they said it takes them 3 mins. 3 minutes is not enough time to properly consider content or what should be included. After we get a few automated portals deleted at MFD and the VP discussion reaches some closure I feel strongly we need to delete all the automated portals as a really bad idea. The template that automates these is up for deletion at [[Wikipedia:Templates_for_discussion/Log/2019_February_28#Template:Basic_portal_start_page]] Further, even though TTH disputes semi-automated creation here he says he uses "semi-automated methods of construction" and is using a "alpha-version script in development that speeds the process further" [https://en.wikipedia.org/wiki/User_talk:CASSIOPEIA/Archive_17#Creating_portals] [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 03:12, 1 March 2019 (UTC) *'''Comment'''. I need to investigate this issue a little further, but I was quite concerned about this before I even saw the thread, because I discovered [[Portal:Ursula K. Le Guin]] a few months ago. I've written a considerable portion of the content about Le Guin on Wikipedia, and even I think it's too narrow a topic for a portal; and when I raised this on the talk page, Transhumanist didn't respond, though they've been active. Transhumanist has been around for a while, so if they're willing to voluntarily stop creating portals while guidelines are worked out, I don't see a need for a formal restriction. <span style="font-family:Papyrus">[[User:Vanamonde93|Vanamonde]] ([[User Talk:Vanamonde93|Talk]])</span> 03:21, 1 March 2019 (UTC) *'''Response from The Transhumanist''' &ndash; The proposer of the hiatus, [[User:UnitedStatesian]], acknowledged that my efforts have been in good faith. Note also that no rules have been broken (to my knowledge) &ndash; I carefully went over the existing mass creation rule and portal scope rule before starting. I have been a participant in the hiatus of mass creation discussion, and have voluntarily ceased portal creation since Feb 21, so as not to aggravate the other participants of that discussion. (What purpose would that serve?) I wish the matter to be resolved as much as anyone else. Since scope is actively being discussed over at the portals guideline talk page, it makes little sense to create pages that might be removed shortly thereafter based on new creation criteria. I plan on participating in the discussions, perhaps continue working on (existing) portals, and I have no plans to defy the mass creation hiatus. Nor do I plan on pushing the envelope any further. The VPR community has expressed a consensus that mass creation be halted. Robert McClenon is seeking to go beyond community consensus specifically to stop me from creating any portals at all, which is not what the community decided. If editors in general are allowed to create portals, just not mass create them, as the response to UnitedStatesian's proposal has indicated, why should I be singled out here? A topic-ban would be unjustified given the circumstances, and would be punitive in nature. In such a case, I would like to know what I was being punished for. Sincerely, <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 06:20, 1 March 2019 (UTC) :*{{yo|The Transhumanist}} I <u>do not share @[[User:UnitedStatesian|UnitedStatesian]]'s assumption of good faith</u>. The evidence which I have seen points far too strongly in the other direction for me to sustain that assumption. ::Please see for example [[Wikipedia:Miscellany for deletion/Portal:University of Fort Hare|MFD: Portal:University of Fort Hare]]. I find it impossible to believe that a remotely competent editor acting in good faith could have created that portal-to-nowhere. If there is some good faith explanation which i overlooked, then I will enjoy hearing it ... but for now, that page looks like just one of many of examples of TTH intentionally creating utterly useless portsalspam in flagrant disregard of any version of the much-hacked portal guidelines, let alone the clear community consensus for selectivity in portal creation as expressed at [[WP:ENDPORTALS]]. ::As others have noted, this is not TTH's first rampage of disruption. If there really was good faith this time, then TTH needs to urgently some serious explaining of their actions, because they <u>do not look like the good faith conduct of a competent editor</u>. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 02:07, 16 March 2019 (UTC) *'''Support''', given Transhumanist's utter refusal to listen to the input at the Proposals thread during the last days, or anywhere else for that matter. Would go further and support a full, indefinite topic ban or even site ban. Every time I've encountered The Transhumanist over the years, it was invariably over some pattern of mindless mechanistic mass creation of contentless pages, which he then kept pushing aggressively and single-mindedly into everybody's face. [[User:Future Perfect at Sunrise|Fut.Perf.]] [[User talk:Future Perfect at Sunrise|☼]] 07:18, 1 March 2019 (UTC) And now we need to clean this mess up. It took me far too long to find and bundle thirty pages for deletion at [[Wikipedia:Miscellany for deletion/Districts of India Portals]] compared to the 3 minutes a piece he took to create them, but better to head this off before he starts into the other 690 odd Indian districts. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 07:33, 1 March 2019 (UTC) *'''Quoted Comment on scale of this issue''' "Since July 1st (after [[WP:ENDPORTALS]] was over), over 4500 portals, excluding redirects, have been created ([[quarry:query/33793]]); the Transhumanist created more than 3500 ([[quarry:query/33795]]); of those, at least 561 were created with a summary along the lines of {{tq|Started portal, in tab batch save, after batch was inspected: image slideshow minimum 2 pics, no empty sections. No visible formatting or Lua errors upon save, but there may be intermittent errors; report such bugs at WT:WPPORTD so that they can be fixed. Thank you.}} ([[quarry:query/33794]]). Just a note --[[User:DannyS712|DannyS712]] ([[User talk:DannyS712|talk]]) 04:42, 27 February 2019 (UTC)" (end quote) |This off a base of just under 1800 Portals existing in July 2018. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 07:49, 1 March 2019 (UTC) *'''Support'''. Given the history with TTH—who has been pulling this same kind of "create an unwanted megaproject, force it through without discussion, and expect the rest of us to waste our time maintaining it" stunt for well over a decade (anyone remember [https://en.wikipedia.org/w/index.php?title=Special:Undelete&target=User%3ASharkface217%2FAwards+Center&timestamp=20080621021418 The Award Center]? [[Wikipedia:WikiProject Outlines]]? [https://en.wikipedia.org/w/index.php?title=Special:Undelete&target=Wikipedia%3AAdmin+school&timestamp=20061107125742 The Admin School]?) and always tries the same "well, it wasn't explicitly banned so I assumed it was what you wanted" defense when called out on it, I'd strongly support a full and permanent topic ban and wouldn't be opposed to a site ban; anyone who's been here for as long as TTH and still can't see the issue with [[Portal:Yogurts]], [[Portal:Rutland]] or [[Portal:A Flock of Seagulls]] is someone who's either being intentionally disruptive or is wilfully refusing to abide by Wikipedia's norms.&nbsp;&#8209;&nbsp;[[User:Iridescent|Iridescent]] 08:26, 1 March 2019 (UTC) *'''Support''' temporary topic ban as a first step. We can look at a site ban if he ignores the topic ban. [[User:PhilKnight|PhilKnight]] ([[User talk:PhilKnight|talk]]) 08:32, 1 March 2019 (UTC) *'''Support''' A topic ban is necessary while the issue is discussed and mass deletion considered. Adding thousands of inadequate and unmaintainable pages is not helpful. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 09:39, 1 March 2019 (UTC) * '''Support''' A generally-accepted principle of Wikipedia editing is that people who add content, and especially established editors, help to maintain it. Even assuming the best about Transhumanist here, I can't see how they can possibly do this with all these obscure portals. A ban on creating more of them has to be the first step. [[User:Nick-D|Nick-D]] ([[User talk:Nick-D|talk]]) 10:07, 1 March 2019 (UTC) * '''Reluctantly Support''' We need time to curate and prune the low-quality portals, otherwise someone will panic and start deleting portals outright. 1:1 (topic to portal) parity is a nice goal, but it isn't readily achievable without the content to fill those portals.--<span style="text-shadow:#FFD700 0.2em 0.2em 0.2em">[[User:Auric|<span style="color: #FC3700;">'''Auric'''</span>]] [[User talk:Auric|<span style="color: #0C0F00;">''talk''</span>]]</span> 11:33, 1 March 2019 (UTC) *'''Support''': TTH's approach seems rather cavalier at the moment. A change, as they say, is as good as a test. [[User:Serial Number 54129|<span style="color:black">'''——'''</span>]][[Special:Contributions/Serial Number 54129|<span style="color:black">''SerialNumber''</span>]][[User talk:Serial Number 54129|<span style="color:#8B0000">54129</span>]] 11:58, 1 March 2019 (UTC) *'''Oppose/Wait''' Let's see if he complies with the eventual results of the discussion at VPP. If he voluntarily agrees to stop, based on community input, then sanctions are not necessary. This seems like overkill right now. First, let the community guidelines pass, THEN let him violate those before we rush to ban. --[[User:Jayron32|<span style="color:#009">Jayron</span>]][[User talk:Jayron32|<b style="color:#090">''32''</b>]] 12:13, 1 March 2019 (UTC) *'''Massive Oppose - why the rush to ban?''' - the idea that giving a TBAN is the only appropriate means is bonkers - there are ongoing discussions. Currently you are trying to TBAN someone who hasn't broken policy. Let's get the agreements in, see if they stop and only then make any action [[User:Nosebagbear|Nosebagbear]] ([[User talk:Nosebagbear|talk]]) 12:18, 1 March 2019 (UTC) *'''Comment''': Isn't this all very similar to the mass creation of "Outline of" articles by The Transhumanist that met with the same kind of opposition (and tanked an [https://en.wikipedia.org/wiki/Wikipedia:Requests_for_adminship/The_Transhumanist_6 RFA]) 10 years ago? If so, then I'd say a topic ban might be in order.--[[User:Atlan|Atlan]] ([[User talk:Atlan|talk]]) 13:37, 1 March 2019 (UTC) :*Yes, it is. [[User:UnitedStatesian|UnitedStatesian]] ([[User talk:UnitedStatesian|talk]]) 03:26, 3 March 2019 (UTC) *'''Support''' this or a complete topic ban from portals or if this continues simply a complete ban. His latest reply [https://en.wikipedia.org/w/index.php?title=Wikipedia%3AVillage_pump_%28proposals%29&type=revision&diff=885662927&oldid=885656985 here] shows such a complete [[WP:IDHT]] attitude, with utterly founded claims about the need to have thousands of portals to be able to find and fix issues (even though many of the now reported issues appear in portals from months ago already), and on the other hand that they have now trouble finding and fixing flaws: "With Legacypac and others actively nominating the new portals for deletion at MfD, our opportunities for improving them and discovering and fixing design flaws are diminishing quickly.", even though perhaps 2 or 3% of the new portals have been nominated, and more than enough similar problematic ones remain to work on (e.g. the inclusion of a DYK which links to [[red herring]] on the [[Portal:Forage fish]]...). Statements like "Legacypac's approach is to recommend deletion of the new type of portal due to design flaws such as this. " shows a thorough lack of understanding of why these MfDs are made and why so many people support them. The designs flaws are just a small part of the reason for deletion, the lack of interest in, maintenance of, and contents for many of these portals are much more important. [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 14:11, 1 March 2019 (UTC) ** The MfDs are potentially a prelude to [[User:DannyS712/rfc4|an RfC]], which may accelerate the process of deletion. With that in mind, the potential shrinkage is worrisome. I'm so tired, I forgot to mention it above. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 14:42, 1 March 2019 (UTC) :::The MfDs, which impact a tiny portion of these creations but a decent sample of various types of topics, are very useful for finding out what the community finds acceptable or desirable. The MfDs are consensus building (something you forgot/ignored). Soon we will be able to craft acceptance and deletion criteria based on the MfD results. That's how notability and other guidelines get developed, precedent. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 15:23, 1 March 2019 (UTC) *'''Unnecessary''' Yes, they need to stop, but they have already agreed to do so (see above: {{tq| <nowiki>[I]</nowiki> have voluntarily ceased portal creation since Feb 21, so as not to aggravate the other participants of that discussion}}), and they are unlikely to kneecap themselves by continuing under the massive scrutiny now present. Let's be civil and spare them the block log entry. Current discussion should drive the portal thing towards some practical steps that will likely include the deletion of most of the offending portals, and some agreed-on guideline that prevents mass creation from occurring again. Let's focus on that. --<span style="font-family:Courier">[[User:Elmidae|Elmidae]]</span> <small>([[User talk:Elmidae|talk]] · [[Special:contributions/Elmidae|contribs]])</small> 14:44, 1 March 2019 (UTC) *'''Support''' – The unilateral creation of thousands of portals must stop. This has been driven largely by one editor, who has made the creation and preservation of portals his or her singular objective. We've seen since the portals RfC that this user will stop at nothing to continue the march of portals...regardless of community concerns, and regardless of Wikipedia policies and guidelines. Removing him or her from the portal topic area is the only way to prevent further disruption. [[User:RGloucester|<span style="font-family:Monotype Corsiva;font-size:12pt;color:#000000">RGloucester </span>]] — [[User talk:RGloucester|☎]] 15:09, 1 March 2019 (UTC) *'''Support''' - as a first step. [[User:Beyond My Ken|Beyond My Ken]] ([[User talk:Beyond My Ken|talk]]) 18:42, 1 March 2019 (UTC) *'''Unnecessary''' per Elmidae. [[User:Enterprisey|Enterprisey]]&nbsp;([[User talk:Enterprisey|talk!]]) 19:31, 1 March 2019 (UTC) *'''Unnecessary''' at this time as the editor has already agreed to stop and discussions are ongoing. [[User:Jonathunder|Jonathunder]] ([[User talk:Jonathunder|talk]]) 22:45, 1 March 2019 (UTC) *'''Support''' Although TTH seerms to be acting in good faith {{they|The Transhumanist}} just don't know when to stop, so the community has to do it for them. [[User:Miniapolis|'''''<span style="color:navy">Mini</span>''''']][[User_talk:Miniapolis|'''''<span style="color:#8B4513">apolis</span>''''']] 23:50, 1 March 2019 (UTC) *'''Reluctant support''' TT was one of the first users that was ever nice to me, many years ago, so I'd really rather not, but this is way out of line. Personally tripling the number of portals, a WP feature that almost nobody uses, and with apparenrly very little consideration to what subjects actualy merit a portal is grossly iresponsible. I get that they were upset at the proposed removal of portals, but this is a ridiculous overreaction that benefits nobody, and if they can't see that then a formal restriction is necessary. [[User:Beeblebrox|Beeblebrox]] ([[User talk:Beeblebrox|talk]]) 18:20, 2 March 2019 (UTC) *'''Support''' per [[User:Fram]], and I assume it would also cover conversions of "old-style" Portals to the problematic one-page versions, as well as adding portal links to any article in the mainspace, and all other Portal-related editing. [[WP:IDHT]] is spot on: in all of these Portal-related discussions, TTH has again shown what is to me a shocking failure of self-examination: no "Gee, this is '''another''' case where a broad swath of the community seems to have a major issue with my behavior, and thus should cause me to step back and assess whether there is 1) anything that, in retrospect, I should I have done differently, and 2) anything I can do ''now'' to a) try to mitigate the damage and/or b) regain the communitity's good favor." TTH's factual statement that "I have not created any new Portals since Feb. 21" is meaningless as a commitment to future behavior. [[User:UnitedStatesian|UnitedStatesian]] ([[User talk:UnitedStatesian|talk]]) 03:24, 3 March 2019 (UTC) *'''Unnecessary''' As mentioned above, moving to preemptively TBAN an editor who has already agreed to stop while discussion is underway serves no purpose here. If they choose to ignore the community consensus, then we can discuss further preventative measures, but doing so now is premature. <small>As an aside, most of those red errors that are being reported are simple fixes, so anyone who finds one can post a note on [[WT:WPPORT]] for one of our editors to fix, or simply add {{para|broken|yes}} to the {{tl|Portal maintenance status}} template at the top of the page.</small> — AfroThundr <sup>([[User:AfroThundr3007730|u]] · [[User talk:AfroThundr3007730|t]] · [[Special:Contributions/AfroThundr3007730|c]])</sup> 06:11, 3 March 2019 (UTC) * '''Support'''. Portals have not been working for for 13 years. A pause of 3 months is more than reasonable. --[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 04:47, 4 March 2019 (UTC) * Mass portal creation should be consider foul of [[Wikipedia:MEATBOT]]. Before continuing, I suggest seeking approval at an RfC, followed by the standard Bot approval process. --[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 04:59, 4 March 2019 (UTC) *'''Support''' This is a long-term problem with TTH. It used to be "Outline" pages, & maybe still is. He is always polite & cheery, but completely ignores all criticism and pushes on with his agenda, as his rather scary newsletters show. [[User:Johnbod|Johnbod]] ([[User talk:Johnbod|talk]]) 15:50, 7 March 2019 (UTC) *'''Support'''. Sensible proposal. Agree with UnitedStatesian that this should also cover conversions of old-style portals. [[User:Feminist|feminist]] ([[User talk:Feminist|talk]]) 05:04, 8 March 2019 (UTC) *'''Oppose''' TTH has agreed to stop for now, he doesn't need a formal ban when he's already doing it voluntarily. [[User:SemiHypercube|<b style="color:#090">Semi</b>]][[User talk:SemiHypercube|<i style="color:#099">Hyper</i>]][[Special:Contributions/SemiHypercube|<u style="color:#009">cube</u>]] 17:01, 8 March 2019 (UTC) *'''Oppose''' Per [[Elmidae]], the user has agreed to stop and has not formally violated policies. We don't need more portals and this behavior needs to stop, but it seems that this has already been achieved for now while discussion is ongoing. — [[User:MarkH21|MarkH21]] ([[User talk:MarkH21|talk]]) 08:12, 12 March 2019 (UTC) *'''Oppose''' - Uneccessary, as The Transhumanist has already ceased such activities (i.e. {{tq|I have ... voluntarily ceased portal creation since Feb 21 ... I have no plans to defy the mass creation hiatus [that has already been established].}}). <small>—&nbsp;[[User:Godsy|<span style="color:#39A78E;">'''Godsy'''</span>]]<sup>&nbsp;([[User talk:Godsy|TALK]]</sup><sub style="margin-left:-2.0ex;">[[Special:Contributions/Godsy|<span style="color:#DAA520;">CONT</span>]])</sub></small> 00:04, 13 March 2019 (UTC) *'''Oppose'''. This is circular reasoning. "I don't think we should have these portals, and others disagree, so I want to punish/shame my principal opponent in hopes of winning." This is several forms of [[red herring]] fallacy all at once (including {{lang|la|[[argumentum ad baculum]]}}, [[appeal to spite]], [[poisoning the well]], and [[traitorous critic]]). {{em|If}} consensus firmly decides we don't want these portals, and {{em|then if}} an editor were to defy that decision and create a bunch more portals of exactly the sort we decided were unwanted, {{em|only then}} would a topic-ban of any kind be warranted. <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 16:54, 14 March 2019 (UTC) *'''Strong support''' this and any other restraint on TTH, up to and including perma-siteban. After stumbling on some micro-portals and MFDing them, I spent a lot of time in September last year discussing these issue ubsuccessfully with the Portals project (see e.g. [[WT:WikiProject_Portals/Archive 7#Portal_Wish_List]], [[WT:WikiProject_Portals/Archive 7#BrownHairedGirl's_agenda]], more at [[WT:WikiProject Portals/Archive 8]]) :It was absolutely clear throughout those discussions that TTH had no regard to the balance of opinion in last years RFC, and repeatedly personalised all reasoned criticism of his conduct as "bias", "personal attack" or "bullying" :There were a few other voices in those discussions who urged restraint, such as @[[User:Bermicourt|Bermicourt]], but TTH took no notice of any of it. So all that's happening now was flagged well in advance, and TTH paid no heed until a community outcry. TTH is now pledging restraint, but made similar promises back in September which were ignored when when the heat was off. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 07:16, 15 March 2019 (UTC) *'''Strong oppose.''' Respectfully to supporters, this idea of a TB that targets a single editor for something that several of us have been involved with comes off as witch-hunty and scapegoaty. I know [[WP:AGF|that's not what it is]]; however, that is how it seems – at least to me. '''''[[User:Paine Ellsworth|<span style="font-size:95%;color:darkblue;font-family:Segoe Script">Paine&nbsp;Ellsworth</span>]]'''''<small>,&nbsp;ed.&nbsp;&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;</small>&nbsp;<small>09:30, 15 March 2019 (UTC)</small> **That's a really weird oppose, @[[User:Paine Ellsworth|Paine Ellsworth]]. If you know it's not actually witch-hunty and scapegoaty, what's the problem? --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 09:48, 15 March 2019 (UTC) ***uhm, I "know" because I really do want to AGF; the problem is that I can't stand by and watch forty lashes given to someone when I helped tie him to the whipping post, so to speak. Hold us all responsible if you want, but don't single just one of us out for something several of us helped do. Hope that's a bit clearer. Thank you for asking, because I do sometimes have difficulty expressing myself adequately with the written word. '''''[[User:Paine Ellsworth|<span style="font-size:95%;color:darkblue;font-family:Segoe Script">Paine&nbsp;Ellsworth</span>]]'''''<small>,&nbsp;ed.&nbsp;&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;</small>&nbsp;<small>10:15, 15 March 2019 (UTC)</small> ****{{yo|Paine Ellsworth}} thanks for the reply. Can you explain more about what you mean by {{tq|helped tie him to the whipping post, so to speak}}? ::::I am puzzled by it, because while I was aware that a few others [[WP:WPPORT]] supported auto-portals, I was not specifically aware that anyone had encouraged {{yo|The Transhumanist|p=}}'s mass-creation sprees of micro-portals and nano-portals. ::::For example, did you or others support the [https://en.wikipedia.org/w/index.php?title=Special:Contributions&dir=prev&offset=20180912064801&limit=500&contribs=user&target=The+Transhumanist&namespace=100&tagfilter=&newOnly=1&start=&end= this creation of over 40 portals per hour]? ::::Did you or others support or encourage the creation of [[Portal:University of Fort Hare]] (I have now nominated it at [[Wikipedia:Miscellany for deletion/Portal:University of Fort Hare|MFD: Portal:University of Fort Hare]]), which was literally a portal to nowhere? ::::I ask this, because it seems to me that there is in fact massive gap between the culpability of a) those WP:WPPORT members who supported creating far more more portals than the community supports; and b) TTH, who repeatedly rapid-created created portals which unavoidably meet [[WP:P2]]. ::::That's why I think it's fair to single out TTH. But if I have misunderstood the gap in responsibility, please correct me. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 01:25, 16 March 2019 (UTC) :::::Responsibility begins with the discussion that saved the portals. It gave the impression that not only was portalspace worth saving, it was worth improving. Then there were those of us who joined the portals project to help when we can, and we did. Perhaps the nom should be held responsible for comparing TTH's actions with the Neelix redirect fiasco? Incomparable, because Neelix created all those filthy dirty redirects all alone, with no help from any members of WikiProject Redirect. TTH had help creating all those filthy dirty portals, though, and with spreading their application. This is outrageously overkill. TTH has ceased making portals all on their own. The nom knows this and yet still had to suggest a topic ban. Why? In my own crummy way of expressing myself with words: pffft! '''''[[User:Paine Ellsworth|<span style="font-size:95%;color:darkblue;font-family:Segoe Script">Paine&nbsp;Ellsworth</span>]]'''''<small>,&nbsp;ed.&nbsp;&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;</small>&nbsp;<small>12:57, 16 March 2019 (UTC)</small> *'''Oppose''' per Elmidae, SMcCandlish and Jonathunder. The user has already voluntarily ceased creating new portals since February 21. There's no need for "the beatings to continue until morale improves". <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 01:39, 16 March 2019 (UTC) ::Northamerican1000 should also be banned from creating more portals. Creating automated navbox portals that overlap existing portal topics is not cool. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 01:46, 16 March 2019 (UTC) :::You need to stop scolding everybody who has ever created a portal. I have breached no policies. <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 02:18, 16 March 2019 (UTC) ::::Au contraire: North, you need to desist from defending this flood of portalspam. Consensus is now clear that it has gone way too far, and a year ago at [[WP:ENDPORTALS]] was very clear that a significant minority of editors supported deleting all portals, while many more supported a purge., Instead you and some others went a spree in the opposite direction. That was at best reckless; at worst, it was wilful disregard of consensus [[WP:CONSENSUS]]. And [[WP:CONSENSUS]] is core policy, so don't push your luck. The guideline [[WP:DE]] is also relevant. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 02:27, 16 March 2019 (UTC) :::::The handful of portals I have created is certainly not a spree. Tired of this typecasting and [[WP:ASPERSIONS]] against any and all portal content creators. Does nothing to improve the encyclopedia. My !vote is regarding the matter at hand regarding TTH; that's it. <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 02:32, 16 March 2019 (UTC) ::::::Tired of you defending the indefensible, and then claiming victimhood when challenged. Portals are ''not'' content, they are a navigational device ... and defending a spammer does nothing to improve the encyclopedia. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 02:46, 16 March 2019 (UTC) *'''Oppose''' TTH stopped created portals when asked and has not resumed since. A topic ban is not needed to stop disruption and imposing one about three weeks after they stopped would be punitive in the extreme. I am though deeply troubled by the personal attacks from some very experienced editors above. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 11:52, 16 March 2019 (UTC) *'''Oppose''' as unnecessary, two weeks after it was proposed. As I understand it, there were multiple editors involved in this effort, so I don't see why we'd TBAN just one. AFAIK the editor at issue has so far kept their promise to stop making portals for the last two weeks. There's no need for a tban right now, as evidenced by the fact that we've had two weeks of discussion on this topic without a tban in place. Nuke the content, not the editor. Of course, that's based on the voluntary self-ban continuing to be observed. If that were to change, so would my !vote. {{nao}} [[User:Levivich|Leviv]]&thinsp;<span style="display:inline-block;transform:rotate(45deg);position:relative;bottom:-.57em;">[[User talk:Levivich|ich]]</span> 17:11, 16 March 2019 (UTC) ==== Lack of good faith from [[User:The Transhumanist]] ==== I posted above[https://en.wikipedia.org/w/index.php?title=Wikipedia:Administrators%27_noticeboard&diff=887974227&oldid=887973757] to dissent from {{yo|UnitedStatesian|p=}}'s assertion that {{lu|The Transhumanist}} has been acting in good faith. I have just encountered a further small example, from today, of TTH's bad faith. In this case, TTH added[https://en.wikipedia.org/w/index.php?title=Portal:University_of_Fort_Hare&diff=887980040&oldid=887973431] the <code><nowiki>nostubs=no</nowiki></code> parameter to [[Portal:University of Fort Hare]], contrary to the general consensus that stubs should not be included in a portal's article lists. I see no evidence that TTH sought a consensus to do so ... and the change was was sneaky, because the edit summary {{tq|add parameter}} did not disclose the nature of the change. Since that portal is being discussed at [[Wikipedia:Miscellany for deletion/Portal:University of Fort Hare|Portal:University of Fort Hare|MFD: Portal:University of Fort Hare]], the change should have been disclosed there. That edit was of course only a small thing, and it has no practical effect because the sum total of non-biographical articles about [[University of Fort Hare]] is 1 (the head article). But at this stage, if TTH was acting with any good faith at all, the appropriate way to demonstrate it would have been to support prompt deletion of this portal-to-nowhere, rather than trying to expand its scope into stubs. I have just checked the last 3 weeks of TTH's contribs, and have found precisely zero instances where TTH has supported the deletion of even the most ridiculously tiny-scope portal which they have created, let alone any instance where they assisted the cleanup by identifying and CSD/MFDing inappropriate creations. I could understand that at this stage TTH might feel dejected by the deprecation of their portalspamming, and prefer to walk away from the topic ... but that explanation for inaction is undermined by a sneaky attempt to rescue a useless portal by adding stubs to its topic list. This breaches the spirit, if not the letter, of the self-restraint which TTH had promised. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 22:15, 16 March 2019 (UTC) {{abot}} ===Proposal 2 - Indefinite ban on page creation === {{collapse top|withdrawn}} {{archive top|insufficient support for this idea. Withdraw [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 13:36, 1 March 2019 (UTC)}} Helpful comments above lead me to these numerous Drafts by [[User:The Transhumanist]] (ranging from 1 to 12 years old) => [[Wikipedia:WikiProject_Outlines#Outline_starts:]]. This is an obsession with mass creation of content no one wants. He has been creating hundreds of useless pages for years and at least 3500 useless automated Portals in the last few months. He has used up his allotment of lifetime page creations on Wikipedia and has a maintenance job to do now on his creations. He should also be working on removal of these useless pages. Therefore I propose a TBAN on page creations in all namespaces, and a TBAN on moves of pages into Mainspace or Portalspace (to prevent the moving of presetup but now empty existing drafts into mainspace), with the following exceptions: Starting an XfD (so he can assist in cleaning yup his mess) and talkpages of other users (for vandal warning etc so he can maintain quality on his creations) and talkpages in general of any existing page. TBAN may be appealed to AN which would want to approve a specific plan for the types of pages he wants to create. *'''Support''' as proposer [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 10:44, 1 March 2019 (UTC) *'''Oppose''' in current form - {{tq|a TBAN on page creations in all namespaces}} is too drastic, give how many [[WP:NS|other namespaces]] that cuts off. I can understand prohibition on mainspace, portalspace, wikipedia space, or even userspace. But TTH not being able to start talk pages? To upload files? To start community books? That's unnecessary. --[[User:DannyS712|DannyS712]] ([[User talk:DannyS712|talk]]) 10:57, 1 March 2019 (UTC) *:I explicitly exempted talkpages of users but modified so all talkpages could be allowed. If he wants to create 500 books he should get permission. If there is a desire to create articles, he could ask for a relaxation, going through AfC for example, but with a preapproved plan. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 11:06, 1 March 2019 (UTC) *'''Oppose''' This seems overbroad, locking down the English Wikipedia over one user. --<span style="text-shadow:#FFD700 0.2em 0.2em 0.2em">[[User:Auric|<span style="color: #FC3700;">'''Auric'''</span>]] [[User talk:Auric|<span style="color: #0C0F00;">''talk''</span>]]</span> 11:20, 1 March 2019 (UTC) *:Umm, this user has a long history of mass page creations. When people object he says no one told him he could not do it. A restriction would not prevent him from creating pages, it would just require him to get the plan preapproved. I don't know what crazy idea he might try next, so block everything except what he gets the community to agree to first. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 11:26, 1 March 2019 (UTC) *::The problem isn’t page creation, it’s MASS page creation (usually using automated tools). Essentially, TTH routinely sacrifices quality for the sake of volume. It is the focus on volume that needs addressing. [[User:Blueboar|Blueboar]] ([[User talk:Blueboar|talk]]) 12:20, 1 March 2019 (UTC) *:::Yes MASS creation. We don't know what he will MASS create next, so let him propose what he wants to create BEFORE he creates it. If his idea is reasonable, great, but if not we save a ton of work and drama. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 12:24, 1 March 2019 (UTC) *::::So why does the original proposal not ban him from mass creation? --[[User:Izno|Izno]] ([[User talk:Izno|talk]]) 13:07, 1 March 2019 (UTC) *::::::Because 3500 pages [https://en.wikipedia.org/wiki/User_talk:CASSIOPEIA/Archive_17#Creating_portals] is not Mass Creation according to his post near the top of the VPP thread: "Please clarify what you mean by "mass creation"; the figure provided above is less than 10 new pages per day per editor, which has never been considered mass creation by any WP standard. Also, please clarify what you mean by "semi-automated", since all software programs, including Wikipedia's internal text editor, may be considered semi-automated. Thank you. — The Transhumanist 19:25, 26 February 2019 (UTC)"[https://en.wikipedia.org/wiki/Wikipedia:Village_pump_(proposals)#Hiatus_on_mass_creation_of_Portals] [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 13:16, 1 March 2019 (UTC) *'''Oppose''' - the problem is limited to the Portal: namespace; there is no evidence provided that there is a problem in any other namespace (I disagree with the foregone conclusion presented about outlines). This is overreaching by a wide margin. [[User:Ivanvector|Ivanvector]] (<sup>[[User talk:Ivanvector|Talk]]</sup>/<sub>[[Special:Contributions/Ivanvector|Edits]]</sub>) 13:33, 1 March 2019 (UTC) {{Archive bottom}} {{collapse bottom}} ===Proposal 3: Relax tagging and notification requirements for Grouped Portal MfDs=== {{collapse top|Withdrawn in favor of Proposal 4}} {{Archive top|Housekeeping withdraw in favor of Proposal 4 [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 13:57, 12 March 2019 (UTC)}} Creating group MfDs for portals is almost as hard as creating one of these portals. If you use twinkle it creates a bunch of redundent discussion pages and floods the creator's talkpage with templates. TheTranshuminist is insisting every page in a group nomination be tagged for deletion [https://en.wikipedia.org/w/index.php?title=Wikipedia%3AMiscellany_for_deletion%2FPortals_for_Portland%2C_Oregon_neighborhoods&type=revision&diff=885793154&oldid=885760666]. He is technically correct, but this generates a lot of extra work for no real benefit. Notifying the creator with the first nom in the group should be sufficient. It is not like there are tons of editors with a vested interest in an [[Wikipedia:Miscellany_for_deletion#Districts_of_India_Portals| a given district of India portal]]. I expect there will be a few more group nominations so addressing this will speed this up. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 18:06, 2 March 2019 (UTC) :Well, there is no notification ''requirement'' that I'm aware of , so I think you can consider that relaxed. Tagging however, is usually considered a hard-and-fast requirement. It isn't exactly fair to discuss deleting a page while not giving any indication to users watching that page. [[User:Beeblebrox|Beeblebrox]] ([[User talk:Beeblebrox|talk]]) 18:13, 2 March 2019 (UTC) :: But in this particular case, there is no realistic expectation that there are any page watchers to begin with, other than the single individual who created them all. [[User:Future Perfect at Sunrise|Fut.Perf.]] [[User talk:Future Perfect at Sunrise|☼]] 18:19, 2 March 2019 (UTC) :::Perhaps any change to the requirements should wait until until it has been agreed which topics merit a portal. There is no urgent need to carry out a mass deletion before deciding what to keep. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 18:22, 2 March 2019 (UTC) ::::Grouped MfDs create precedent and help us create policy based on the result. For example if 20+ District of India portals are deleted at MfD a precident against creation of 690 more such portals has been established. Similarly an effort to create portals on all the counties in the US or regional districts in Canada would be easier to shut down. ::::Given how we found two recently automated now broken portals linked off the Mainpage, is the creator even watching them? ::::The Neelix situation creates precedent for this relaxation. We went even further there and dispensed with discussion. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 18:28, 2 March 2019 (UTC) :::::Thanks for the clarification. Grouping portals which are clearly going to stand or fall together, such as districts of India, makes sense. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 18:38, 2 March 2019 (UTC) '''Oppose.''' Tagging is a requirement, notification is not. And I just completed tagging on all of the Districts of India that are in the bundled nom. Assuming the current crop of MfD's close as delete, the solution is to propose a temporary [[WP:CSD|speedy deletion criterion]] X3. [[User:UnitedStatesian|UnitedStatesian]] ([[User talk:UnitedStatesian|talk]]) 00:47, 3 March 2019 (UTC) : Those portals aren't representative, they're fringe cases. The set of new portals include a wide range of scope, for example, and many had additional work done on them. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 01:45, 3 March 2019 (UTC) :: What do you mean by "fringe cases" and "representative"? They seem very representative to me. [[User:UnitedStatesian|UnitedStatesian]] ([[User talk:UnitedStatesian|talk]]) 03:58, 3 March 2019 (UTC) '''Oppose''' &ndash; Any readers of a page that's up for deletion has a right to know that the page may go bye bye, and that's why the deletion policy requires notice. There's no need to create a separate MfD page for each page being nominated for deletion. Posting a notice on each page that leads directly to the same discussion is easy. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 01:45, 3 March 2019 (UTC) *'''Oppose''' - As [[User:UnitedStatesian]] says, we need X3. [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 04:11, 3 March 2019 (UTC) * The is no need to relax anything. Mass tagging and mass notification is no great issue. If the consensus is that they should all be deleted, Feds them all through mfd in one list. Ask The Transhumanist to tag and notify. I trust that he will cooperate. Stop the panic. —[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 11:27, 4 March 2019 (UTC) {{Archive bottom}} {{collapse bottom}} === Concerning further proposals === {{atop|This is a central community noticeboard, which carries no less inherent validity than VPP. This is the relevant discussion, and as long as wide-reaching proposals are properly advertised to the community, it makes no difference whether they're listed here or at VPP. Wikipedia is [[WP:NOTBUREAU|not a bureaucracy]], and petty procedural objections can never override the process of consensus-building. Please focus on the discussions themselves, rather than meta-discussions regarding procedure. [[User:Swarm|<span style="color:Green">'''~Swarm~'''</span>]] [[User talk:Swarm|<span style="color:DarkViolet">'''{talk}'''</span>]] 05:22, 17 March 2019 (UTC)}} The proper venue for proposals is [[Wikipedia:Village pump (proposals)]]. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 01:49, 3 March 2019 (UTC) ::AN is a perfectly good place for many kinds of proposal. With over 300,000 edits and many years here you should know better. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 03:51, 3 March 2019 (UTC) ::: AN is not the proper place for a proposal on regulating content (referring to portals loosely as content). The way forward does not require administrative action, TTH will respect consensus. —[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 12:18, 4 March 2019 (UTC) ::::@[[User:SmokeyJoe|SmokeyJoe]], I admire your AGF, but I think it is wildly misplaced. ::::This whole drama arose because after a far-from-unanimous RFC consensus not to actually delete the whole portal namespace, TTH chose to invert the meaning of that consensus to "create thousands of crappy new semi-automated microportals at a rate of up 40 per hour" ... and then go batshit raging at anyone who MFed some of the junk or pointed out that the consensus was ''not'' actually for a pressure hose of portalspam. ::::I don't know whether TTH has comprehension issues or just disdains the consensus, but I don't see any other explanation for the last year of TTH antics ... and either way, I see zero reason to expect that TTH will {{tq|respect consensus}}. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 10:50, 15 March 2019 (UTC) ::::: I’ve known TTH for a long time, although I have never worked with him. He always seemed perfectly reasonable, and really interested in a very worthwhile thing, navigation aids. There’s no problem there. I haven’t followed portal discussions closely, but I have never seen TTH rude or obstinate or disdainful. There must have been a misapprehension. I encouraged him to make auto-portals, and he did, and now he is trouble for it. I think the answer is at [[WT:Bots]]. AN should not be for making and implementing portal-specific proposals, the proposals should be directed at TTH. Follow the Bot process for any auto-portal creation. Do not create any new portals without an approved bot. That sort of thing. —[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 12:03, 15 March 2019 (UTC) :'''Wrong venue''' In the nutshell at the top in read mode, and again in bold and red in the edit window, the words scream '''This noticeboard is for <span style="color:red">issues affecting administrators generally</span> – announcements, notifications, information, and other matters of general administrator interest.'''. What we have here is a big idea involving the work of everyone. At most there should be a pointer diff here at AN. [[User:NewsAndEventsGuy|NewsAndEventsGuy]] ([[User talk:NewsAndEventsGuy|talk]]) 12:02, 6 March 2019 (UTC) As I see it the reason this ended up here is because the initial proposal was for a topic ban, which is AN material. The other related proposals were put here for convenience. At any rate it’s not grounds for a procedural close. —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua">&nbsp;python</span><span style="color:#ff0">coder&nbsp;</span></span>]] ([[User talk:pythoncoder|talk]]&nbsp;&#124;&nbsp;[[Special:Contribs/pythoncoder|contribs]]) 01:26, 11 March 2019 (UTC) * This is absolutely the wrong venue, since this is not an "issue affecting administrators generally", it's a proposal (actually a pile of confused and confusing proposals – "I'm not getting my way in version A, so try version B. Nope? Okay, how about C? No? Then here's proposal D ...") that would affect the entire project, and is essentially a content-presentation and navigation matter, not an administrator matter of any kind. This is basically a variant of forum shopping, where instead of moving to a different venue, the idea is dressed up in a new outfit and put before the same venue over and over. The wrong venue. (And is actually regular forum-shopping, too, since we just had a big RfC about this last year.) [[WP:VPPOL]] is the place for something like this, especially since one of the various competing proposals includes making changes to [[WP:CSD]] policy, something we very, very rarely touch and only after considerable site-wide debate and a clear community consensus that it's required and will not have unintended negative consequences (ever noticed that the sequence of lettered and numbered CSD criteria has gaps in it? The community has revoked several CSD criteria as going too far). CSD is pretty much our most dangerous policy. <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 16:59, 14 March 2019 (UTC) ::Yeah, if I were the one starting this, I would’ve put it on one of the village pumps. At any rate it’s on [[T:CD]] now so it will be seen by those who frequent the village pumps (though it won’t show up on watchlists). Also, only two out of the nine [[WP:OCSD|deprecated CSDs]] were repealed for “going too far”. 6/9 were removed because they were redundant and they were folded into other CSDs. This leaves CSD X1, the prototype for the CSD X3 proposal, which was repealed at the conclusion of the Neelix redirect cleanup. —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua"> python</span><span style="color:#ff0">coder </span></span>]] ([[User talk:pythoncoder|talk]] &#124; [[Special:Contribs/pythoncoder|contribs]]) 19:24, 15 March 2019 (UTC) {{abot}} ===Proposal 4: Provide for [[WP:CSD|CSD]] criterion X3=== {{atop|Clearly both sides feel strongly about this question and both have advanced reasonable arguments. Those in favour typically cite the large amount of community effort required to clean up these portals through the normal MfD process, while those against often do not want to see useful content thrown out with the useless. There is also a significant minority who see a need for ''some'' fast-track process to avoid wasting community time, but think that this proposal goes too far. As it seems likely that a less-aggressive proposal would gain the support of most of the supporters as well as a large segment of those opposed, the arguments in opposition seem somewhat stronger. So, while there is a small majority in favour of the change, there is '''no consensus''' for it. [[User:GoldenRing|GoldenRing]] ([[User talk:GoldenRing|talk]]) 10:28, 4 April 2019 (UTC)}} <small>{{admin comment}} This proposal is being advertised at [[WP:VPP]] and [[WP:CD]], and it has been requested that it stay open for at least 30 days. [[User:Swarm|<span style="color:Green">'''~Swarm~'''</span>]] [[User talk:Swarm|<span style="color:DarkViolet">'''{talk}'''</span>]] 05:14, 17 March 2019 (UTC)</small> ---- I am entering and numbering this proposal in order to get it into the record, but am requesting that action on it be deferred until the current round of MFDs are decided. As per [[User:UnitedStatesian]], Create [[WP:CSD|Criteria for Speedy Deletion]] criterion X3, for portals created by [[User:The Transhumanist]] between April 2018 and March 2019. Tagging the portals for speedy deletion will provide the notice to users of the portals, if there are any users of the portals. I recommend that instructions to administrators include a request to wait 24 hours before deleting a portal. This is a compromise between the usual 1 to 4 hours for speedy deletion and 7 days for XFD. The availability of Twinkle for one-click tagging will make it easy to tag the pages, while notifying the users (if there are any). [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 04:21, 3 March 2019 (UTC) : This proposal should be posted in a wider venue, such as [[WP:VPR]] or [[WP:MFD]]. Many of those portals have been in place for months, making WP:AN too narrow a venue for them. CSD notices wouldn't be placed until after the discussion is over, and therefore would not serve to notify the users of those portals of the discussion. A notice to the discussion of this proposal, since it is a deletion discussion, should be placed on each of the portals, to allow their readers to participate in the discussion. The current round of MfDs are not a random sampling of the portals that were created, and therefore are not necessarily representative of the set. The portals themselves vary in many ways, including scope, the amount of time they've been accessed by readers, quality, number of features, picture support, volume of content, amount of work that went into them, number of editors who worked on them, length, readership, etc. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 07:09, 3 March 2019 (UTC) ::How would you suggest to get a representative sample? [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 07:20, 3 March 2019 (UTC) ::: Thank you for asking. That would be difficult now, since there are already a bunch of portals nominated for MfD. If those were included, then the sample would already be skewed. I expect a truly random sample would reveal that some portals are worth keeping and others are not. A more important question would be "How would we find the portals worth keeping? Which is very similar to the question "what should the creation criteria for portals be?", the very thing they are discussing at the portal guidelines page right now. Many of these portals may qualify under the guideline that is finally arrived upon there. For example, they are discussing scope. There are portals of subjects that fall within Vital articles Level 2, 3, 4, and 5, and there are many portals of subjects of similar scope to the subjects at those levels. And many of the portals had extra work put into them, and who knows how many had contributions by other editors besides me. Another factor is, that the quality of the navigation templates the portals are powered by differs, and some of the portals are powered by other source types, such as lists. Some have hand-crafted lists, as there are multiple slideshow templates available, one of which accepts specific article names as parameters. Another way to do that is provide a manual list in the subtopics section and power the slideshow from that. Some of the portals are of a different design than the standard base template. Some are very well focused, contextually, while others are not. For example, some of the portals have multiple excerpt slideshows to provide additional context. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 07:46, 3 March 2019 (UTC) *'''Support''' in principle. Looking at the existing MFD discussions, TTH seems determined to drag and wikilawyer as much as possible to try to derail the discussions, even for blatantly and indefensibly inappropriate microportals like those discussed at [[Wikipedia:Miscellany for deletion/Portals for Portland, Oregon neighborhoods]]; it's not a good use of anyone's time to go through the same timesink 5000+ times. (The cynic in me says that a speedy criterion wouldn't work as while the creators wouldn't be able to decline the templates themselves, TTH and Dreamy Jazz would probably just follow the tagger around removing the speedy templates from each other's creations.) In practice, it would probably be more efficient to do what we did with Neelix and have a streamlined MFD nomination process, in which "created by TTH" is considered sufficient grounds for deletion at MFD and they default to delete unless someone can make a strong argument for keep. MFD is less gameable and also gives a space for people to defend them in those rare cases where they're actually worth keeping. (Every time I look, I find that the flood of inane and pointless TTH portals has spread further than I thought; [[Portal:Intermodal containers|shipping containers portal]], anyone?)&nbsp;&#8209;&nbsp;[[User:Iridescent|Iridescent]] 08:26, 3 March 2019 (UTC) *:Dreamy Jazz seems unlikely do that, having already decided during this debate to stop donating their time to Wikipedia. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 18:06, 3 March 2019 (UTC) * '''Comment''' &ndash; Another option would be to move these to draft space. The templates and lua modules could be modified so that the portals render right in that namespace (I wish I would have thought of this before). Being in draft space would give time to fix their various problems (keeping in mind that micro-scope is not fixable), and identify the ones worth keeping. I would agree not to move any of them personally, and would propose/request such moves after the new creation criteria guidelines for portals are settled upon. I would also be willing to tag those that did not meet those guidelines with CSD (as creator), saving Legacypac the trouble of nominating them at MfD (he mentioned somewhere that he thought I should help clean up this "mess"). Another benefit of this strategy is that if any of them sit in draft space too long without further development, they automatically become subject to deletion per the draft space guidelines, and those that reach that age without any edits can be deleted en masse without time-consuming effort-wasting MfD discussions. This course of action would of course need the participation of some lua programmers to add the necessary functionality to the modules, which would be a good upgrade for those, to allow for portal drafts to be created in the future. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 09:15, 3 March 2019 (UTC) :: P.S. {{ping|Iridescent|Legacypac}} (pinging) <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 09:28, 3 March 2019 (UTC) :*Absolutely not. The problem is that hundreds of portals on obscure topics makes an unmaintainable mess. Passing it to another namespace does not solve the problem which is that the portals are not helpful and are not maintainable. Automated creation of outlines/portals/anything must stop. [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 09:36, 3 March 2019 (UTC) :*No. I tried moving one broken portal to Draft as a test and it broke even more stuff. Not worth the effort to modify everything for draft space and then let the same little group of editors release them willy nilly back into portal space. Since this group ignored their own [[Wikipedia:Portal/Guidelines]] "''portals should be about broad subject areas, which are likely to attract large numbers of interested readers and portal maintainers.''" why should anyone trust them to follow stricter guidelines? [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 09:57, 3 March 2019 (UTC) :*Definitely not. What possible benefit would there be to cluttering up another namespace with ≈5000 pages that will never serve any useful purpose? If you want to goof around with wikicode, nobody's stopping you installing your own copy of Mediawiki; we're not your personal test site.&nbsp;&#8209;&nbsp;[[User:Iridescent|Iridescent]] 15:38, 3 March 2019 (UTC) :*As a general rule, Portal pages should '''not''' be draftified. In fact, we should not usually move anything not designed to be an article to draft space. Draft portals should be in portal space, just like draft books should be in book space and draft templates in template space (pages with subpages are a pain to move, and many namespaces have special features that suggest keeping drafts in the same space if possible). If a portal is not ready for viewing by the general public, tag it with a relevant maintenance template and make sure it is not linked to from mainspace or from other portal pages. :*In the case at hand, TT's mass created portals do not seem like they will all be soon made ready for wider consumption, so deleting them seems the better option. —'''[[User:Kusma|Kusma]]''' ([[User talk:Kusma|t]]·[[Special:Contributions/Kusma|c]]) 20:15, 3 March 2019 (UTC) *'''Support''' nuking from orbit: It's the only way to be sure. [[User:Serial Number 54129|<span style="color:black">'''——'''</span>]][[Special:Contributions/Serial Number 54129|<span style="color:black">''SerialNumber''</span>]][[User talk:Serial Number 54129|<span style="color:#8B0000">54129</span>]] 09:32, 3 March 2019 (UTC) :*How is this the "only way" to be "sure"? What about actually viewing the portals themselves, as opposed to mass deleting them all sight unseen? <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 03:08, 26 March 2019 (UTC) *'''Support''' enough with the wikilawyering and obstruction. This proposal is a little too narrow though - TTH created 3500+ automated portals but others in his little team created around 1000 more. I just grouped some by [[User:Dreamy Jazz]] into [[Wikipedia:Miscellany_for_deletion#US_County_Portals]] [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 09:57, 3 March 2019 (UTC) *'''Support''' These useless broken portals have to go. [[User:CoolSkittle|<span style="background-color: blue; color: orange">CoolSkittle</span>]] ([[User talk:CoolSkittle|talk]]) 14:54, 3 March 2019 (UTC) :*Could you provide any evidence that all of the portals are "broken"? Many of them that I have viewed and used are fully functional, and not broken at all. <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 03:09, 26 March 2019 (UTC) *'''Support''', too many, too quickly, not enough thought went into their creation. Nuke these, revert other portals that were better before TTH "restarted" them. Automation should help with portal maintenance, not replace portal maintenance or move the maintenance burden to navboxes or other places. —'''[[User:Kusma|Kusma]]''' ([[User talk:Kusma|t]]·[[Special:Contributions/Kusma|c]]) 14:57, 3 March 2019 (UTC) *'''Support''', sensible and fair way to deal with these. [[User:Johnbod|Johnbod]] ([[User talk:Johnbod|talk]]) 16:53, 3 March 2019 (UTC) *'''Support''': [[WP:MFD|MFD]] could never handle the overwhelming amount of unnecessary and unsustainable portals, considering the magnitude of TTH's portal creation entering the thousands. –''[[User:EggOfReason|eggofreason]]''<sup>[[User talk:EggOfReason|talk]]</sup> 20:12, 3 March 2019 (UTC) *'''Support''' nuking. [[User:Beyond My Ken|Beyond My Ken]] ([[User talk:Beyond My Ken|talk]]) 20:30, 3 March 2019 (UTC) * Transcluded to [[Wikipedia talk:Criteria for speedy deletion]]. [[User:Pppery|&#123;&#123;3x&#124;p&#125;&#125;ery]] ([[User talk:Pppery|talk]]) 20:31, 3 March 2019 (UTC) *'''Support''' mass creation of portals on these topics isn't appropriate without wider discussion, and the automated/semi-automated method used to create them doesn't produce high quality output. [[Portal:Sierra County, California]], for example, is about a county with a population of 3,240, and consists of the lead of the main article, a few random contextless images grabbed from that article (mostly maps or logos) and portal boilerplate. Cleaning these up will require a temporary speedy deletion criterion, I don't think MfD could handle the load. '''''[[User:Hut 8.5|<span style="color:#FF0000;">Hut 8.5</span>]]''''' 22:25, 3 March 2019 (UTC) *'''Support''' as proposer. I had already suggested deferring, but am satisfied that it is going ahead to mass-delete. I will add that, after a consensus is reached on whether and how to use portals, any that were deleted and are needed are available at [[WP:REFUND|Requests for Undeletion]]. [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 01:01, 4 March 2019 (UTC) * This mass page creation went against [[WP:MEATBOT]] and at least the spirit of [[WP:MASSCREATION]] if not the letter. An appropriate remedy for automated script and semi-automated creation is speedy deletion. Did you know they were driving for 10,000 portals at a rapid pace? It's here [https://en.wikipedia.org/wiki/User_talk:The_Transhumanist#Wikipedia:WikiProject_Portals_update_#029,_13_Feb_2019] [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 04:44, 4 March 2019 (UTC) * '''Oppose''' any and all notions of creating new CSD criteria at any drama board. Discussions here are too rushed, too emotive, too reactionary. Use [[WT:CSD]]. Consider using a WT:CSD subpage RfC. Do not attempt to mandate the detail of policy from a drama board. --[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 04:52, 4 March 2019 (UTC) Transclusion is not good enough. The discussion needs to be searchable from WT:CSD, and the specifics of any and all new criteria need to address the [[Wikipedia_talk:Criteria_for_speedy_deletion/Header|Criteria for a new CSD criterion]]. --[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 04:55, 4 March 2019 (UTC) ::Many editors at the Village Pump discussion, the Tban discussion above, and at MfDs also supported this. We do not need to fragment this discussion further. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 05:12, 4 March 2019 (UTC) ::: Proposal 1 will make this Proposal 4 moot. This Proposal 4 is not a proper CSD implementation. --[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 05:41, 4 March 2019 (UTC) ::::{{reply|SmokeyJoe}} Proposal 1 is about stopping TTH from creating new portals. Proposal 4 is about deleting those he created in the last couple of months. How is P1 going to make P4 moot? —'''[[User:Kusma|Kusma]]''' ([[User talk:Kusma|t]]·[[Special:Contributions/Kusma|c]]) 10:19, 4 March 2019 (UTC) ::::: List them all in an MfD, if they must all be deleted. A CSD that enables self appointed decision makes for which should go and which might be ok, is inferior to MfD. MfD can handle a list of pages. —[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 11:24, 4 March 2019 (UTC) ::::::If you want them all at MfD stop objecting to the listing of specific Portals at MfD. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 01:34, 11 March 2019 (UTC) ::::::: No. Some of the Portals I would support for deletion, and others definitely no. This makes the proposal for a CSD invalid. It fails the CSD new criterion criteria. The proposal is neither '''Objective''' or '''Uncontestable'''. It would pick up a lot of portals that should '''not''' be deleted. --[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 01:44, 11 March 2019 (UTC) * '''Support'''. No care at all went into these portals, they are mindless creations with loads of errors and little actual benefit for our readers. I would also support the restoration of all pre-existing portals to the pre-transhumanist version, the new "single page" version may require less maintenance, but is way too often clearly inferior (see e.g. [https://en.wikipedia.org/w/index.php?title=Portal%3ABelgium&type=revision&diff=879620092&oldid=869200880 this], which is more like vandalism than actual improvement, and has been reversed since). [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 10:31, 4 March 2019 (UTC) *'''Comment'''. Anyone restoring old multi-subpage portals should bear in mind that they will require maintenance. If there is no-one willing to maintain them, they, too are likely to be MfDed. No old-style portal with a willing and active maintainer has been converted as far as I know, so I suggest that anyone restoring them should be willing to maintain them. &middot; &middot; &middot; [[User:Pbsouthwood|Peter Southwood]] [[User talk:Pbsouthwood|<sup>(talk)</sup>]]: 16:47, 4 March 2019 (UTC) **No. Converting an unmaintained but well-designed portal into an unmaintained semi-automated worse portal is not the way forward. Any claims that the new portals are maintained or don't need maintaining is false, as the many problematic new portals demonstrate. [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 17:00, 4 March 2019 (UTC) **[[Portal:Germany]] was converted (more than once) although it has maintainers. To make sure your portal isn't "improved", you need to put a specific template on the page, which isn't very obvious. There are old-style multi page portals that require only minimal maintenance, and where the conversion removed specific features. All those should be reverted, also to protect the subpages from overzealous deleters (the worst is deleting the /box-footer subpages; this breaks all old revisions by removing a necessary closing div). —'''[[User:Kusma|Kusma]]''' ([[User talk:Kusma|t]]·[[Special:Contributions/Kusma|c]]) 17:21, 4 March 2019 (UTC) *'''Oppose''' A mass-deletion of the new generation portals. Listing them at MfD will be sufficient for any that do not meet the criteria laid out in the [[WP:PORTG|portal guidelines]] (which are still [[WT:PORTG|under discussion]]). It makes little sense to remove the whole batch because some of them are problematic. They would need to be properly triaged to ensure the good ones are not caught in the process. <small>I would of course, help with said triage. We're not trying to create more work for the community, just preserve good content.</small> — AfroThundr <sup>([[User:AfroThundr3007730|u]] · [[User talk:AfroThundr3007730|t]] · [[Special:Contributions/AfroThundr3007730|c]])</sup> 23:47, 4 March 2019 (UTC) *'''Comment''' - You created more work for the community by creating thousands of portals, some of which do not work, and with no intention to maintain them. I see no evidence that this effort created good content that needs to be preserved. [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 04:17, 5 March 2019 (UTC) :There is no new content in the automated portals, it's all poorly repackaged bits of existing content. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 04:40, 5 March 2019 (UTC) ::All portals, old or new, good or bad, manual or automated, repackage existing content. That's their job. New content belongs in articles. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 11:20, 5 March 2019 (UTC) *'''Strong oppose''' per wumbolo below: [[WP:CSD#P2|criterion P2]] already covers a number of these, the rest should be discussed. I still stand by my original comment which follows this addition. [[User:Wugapodes|Wugapodes]] [[User talk:Wugapodes|[t<sup>h</sup>ɑk]]] [[Special:Contributions/Wugapodes|[ˈkan.ˌʧɹɪbz]]] 22:37, 13 March 2019 (UTC) Original comment: Weak oppose on principle. CSD is a [[WP:IAR|necessary evil]], and I don't think we should be hasty to add another criterion that skips our usual consensus process. I'm fine with nuking these portals and not opposed to deleting them, any diamonds in the rough will prove their worth by being created again, but I would prefer one big MfD with the rationale "created by The Transhumanist" which allows proper determination of consensus and gives those who want to spend their time triaging a chance to do so. [[User:Wugapodes|Wugapodes]] [[User talk:Wugapodes|[t<sup>h</sup>ɑk]]] [[Special:Contributions/Wugapodes|[ˈkan.ˌʧɹɪbz]]] 08:03, 5 March 2019 (UTC) ::Building multipage MfDs like [[Wikipedia:Miscellany for deletion/Portals for Portland, Oregon neighborhoods]] is time consuming and tedious. A temporary CSD is rhe way to go. Consensus against this mess of new portals has already been established at VP, AN and in the test MfDs. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 17:20, 5 March 2019 (UTC) *'''Support''' due to the massive amount of time it would take to put the ~4500 portals through MfD. MfD has been swamped with portal deletion requests from some time ago, and I can't see all this stuff removed via MfD in the foreseeable future (as someone said earlier, there is still a lot of Outlines left over from one of TTH's previous projects, so who knows how long it would take for MfD to delete all of this). This CSD X3 would streamline the process, and it would probably only take a few days to a week. It would help, as also mentioned earlier, to extend the criterion to the other users involved in the mass creation of these portals. [[User:Rlin8|Rlin8]] ([[User talk:Rlin8|☎]]·[[Special:Contributions/Rlin8|✎]]·[[Special:EmailUser/Rlin8|📧]]) 03:31, 6 March 2019 (UTC) ** MfD has never had an issue to nominations of list of pages. 4500 separate MfD nominations would be absurd, but a list would be OK. If each is new, and has a single author, notifications of the author will be trivial. A CSD proposal shortcuts a discussion of the merits of the new portals, and pre-supposes deletion to be necessary, contrary to [[WP:ATD|deletion policy]]. --[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 04:01, 6 March 2019 (UTC) ::TTH demands we place notification on every portal. We can skip notifying him, but building even 20 page MfD's is very time consuming. How do you propose to discuss 4500 or even 100 assorted portals at a time? These took 3 min to make - but far more than 3 min to list, tag, discuss and vote, then delete - when you add up all the time required from various editors and Admins. The test MfDs are sufficent and the very strong opposition to this automated portal project justifies this temporary CSD. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 04:16, 6 March 2019 (UTC) ::: "TTH demands we place notification on every portal"? [[User:Legacypac|Legacypac]], I have missed that post by him. If he did that, it needs to be repudiated. If these are new pages, and he is the only author, it is sufficient to notify him once. If all 4500 are essentially variations on the same thing, as long as the full set is defined, and browsable, we can discuss them all together at MfD. --[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 05:34, 6 March 2019 (UTC) ::::[[User:SmokeyJoe]] during the Portland Oregon neighborhood MFD I specifically said I was not tagging all the related portals but he insisted I tag here [https://en.wikipedia.org/w/index.php?title=Wikipedia:Miscellany_for_deletion/Portals_for_Portland,_Oregon_neighborhoods&diff=next&oldid=885760666] I could not get support in the section above to relax the MfD tagging because others wanted this CSD. During the Delete Portals RFC TTH went all out insisting every portal including the community portal be tagged for deletion - then he did it himself. That brought in all kinds of casual infrequent editors who were mostly against deleting the community portal. (Even though that was Pretty much pulled out of consideration for deletion before the tagging project). That massive tagging derailed the deletion RFC. By making cleanup as hard as possible TTH is making a lot of people want to nuke everything. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 06:11, 6 March 2019 (UTC) ::::: Legacypac's analysis is erroneous and misleading. The [[WP:ENDPORTALS]] RFC was a deletion discussion, and posting a notice on each page up for deletion is required by deletion policy. Note that the Community Portal was only mentioned twice. A portal that was the basis for about 50 oppose votes was the Current Events portal. Neither the Community Portal nor the Current Events portal were exempted in the proposal at any time. If you didn't count those, that left the count at about 150 in support of eliminating portals to about 250 against. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 07:25, 6 March 2019 (UTC) :::: {{ping|SmokeyJoe}} (edit conflict) See the top of this section for the referred to statement, which is not exactly as he quoted. A notice posted at the top of the portals slated by this proposal would be appropriate. Legacypac has been posting notice for his multi-page nominations using the {{tl|mfd}} template, which auto-generates a link to an mfd page of the same title as the page the template is posted on. Rather than following the template's instructions for multiple pages, he's been creating an MfD page for each, and redirecting them to the combined mfd. Then a bot automatically notifies the creator of each page (me), swamping my user talk page with redundant notifications. Thus, Legacypac believes he'll have to create thousands of mfd redirect pages, and that I somehow want 3500+ notifications on my talk page. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 07:10, 6 March 2019 (UTC) ::::::You want us to manually tag pages for deletion that you used an automated script to create? You flooded Wikipedia with useless pages in violation of [[WP:MEATBOT]] but you are worried about having to clean up your talkpage notices? Just create an archiving system for your talkpage like we did for [[User:Neelix]]'s talkpage. If you don't want notices you could start tagging pages that fail your own guidelines with "delete by author request" instead of commenting on how we will do the cleanup. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 08:37, 6 March 2019 (UTC) :::::::TTH, if you don't want so many deletion notices on your talk page, then remember in future not to create thousands of spam pages. Please help with the cleanup, rather than complaining about it. :::::::@[[User:Legacypac|Legacypac]]: good work MFDing the spam, but it does seem that you are using a somewhat inefficient approach to tagging. Have you tried asking at WP:BOTREQ for help? In the right hands, tools such as AWB make fast work of XfD tagging. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 12:21, 15 March 2019 (UTC) *'''Support''' whatever course of action that will result in every portal created in this manner being deleted with the minimal of time and effort required. TTH has set up his automated tool, created a massive mess, and left it unattended for others to sort out. It should take less time to clean up this mess than it did to make it, not more. Nuke the lot and if there is anything of value lost then TTH can manually request pages to be restored one at a time at DRV. <u style="text-decoration:none;font:1.1em/1em Arial Black;letter-spacing:-0.09em">[[User:Fish and karate|<u style="text-decoration:none;color:#38a">Fish</u>]]+[[User_talk:Fish and karate|<u style="text-decoration:none;color:#B44">Karate</u>]]</u> 11:19, 6 March 2019 (UTC) *'''Support''' per Fish and karate. [[User:RGloucester|<span style="font-family:Monotype Corsiva;font-size:12pt;color:#000000">RGloucester </span>]] — [[User talk:RGloucester|☎]] 14:20, 6 March 2019 (UTC) *'''Strong oppose''' as written. I could support something that explicitly excluded portals which are in use and/or are being developed, but the current proposal to indiscriminately delete everything, including active portals, unless the admin chooses to notify any editors and the ones notified happen to be online in a narrow time frame is significantly overly broad. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 01:54, 7 March 2019 (UTC) *'''Support''' Not that portals are that bad, but I don't think we need portals on smaller subjects. ([[Portal:Spaghetti]] when we already have [[Portal:Pasta]]? [[Portal:Nick Jr.]], anyone?) Some might be worth keeping, but a lot are unneeded and unmaintainable. At least it's not a {{u|Neelix}} case. [[User:SemiHypercube|<b style="color:#090">Semi</b>]][[User talk:SemiHypercube|<i style="color:#099">Hyper</i>]][[Special:Contributions/SemiHypercube|<u style="color:#009">cube</u>]] 16:57, 8 March 2019 (UTC) **{{replyto|SemiHypercube}} "Some might be worth keeping" is actually an argument against this proposal. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 12:08, 11 March 2019 (UTC) ***{{re|Thryduulf}} Kind of, but that might be a reason not to just mass delete all at once. In the Neelix case there ''were'' some redirects that were actually useful, so a separate CSD criterion was used to keep some redirects at the admins' discretion, so this might be a similar case (before you say that contradicts my "it's not a Neelix case" statement, I meant that in terms of what the redirects were about) [[User:SemiHypercube|<b style="color:#090">Semi</b>]][[User talk:SemiHypercube|<i style="color:#099">Hyper</i>]][[Special:Contributions/SemiHypercube|<u style="color:#009">cube</u>]] 12:23, 11 March 2019 (UTC) ***It violates points 1 and 2 of the requirements for CSD criteria: objectivity and unconestability. Unless ''all'' the portals covered should be speedily deleted then ''none'' of them should be. If you only want to delete some of them then you should be opposing this criterion (just like you should have opposed the subjective Neelix criterion). [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 12:34, 11 March 2019 (UTC) <s>*'''Support''' Only realistic way to deal with these. [[User:Johnbod|Johnbod]] ([[User talk:Johnbod|talk]]) 01:57, 11 March 2019 (UTC)</s> <small>Duplicate !vote struck. [[User:GoldenRing|GoldenRing]] ([[User talk:GoldenRing|talk]]) 10:16, 4 April 2019 (UTC)</small> *'''Request the posting of a notice at the top of each of the pages being nominated here for mass deletion, as required by the Deletion Policy.''' This proposal is currently a gross violation of the deletion policy because it is a discussion to delete 3500+ pages, that have been created over the span of a year, that are presently being viewed hundreds of thousands of times per month (projected to millions of times over the coming year) by readers of Wikipedia. The proposal for mass deletion has been made without the required notice being posted at the top of the pages to be deleted. This is being decided by a handful of editors unbeknownst to the wider community, namely, the readership of the portals to be deleted. It may be that those reading such notices would decide that the portals should be deleted, but the point here is that you are denying them the opportunity to participate in the deletion discussion as required by the deletion policy. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 21:12, 11 March 2019 (UTC) ::Request you stop wasting people's fucking time. [[User:Only in death|Only in death does duty end]] ([[User talk:Only in death|talk]]) 21:41, 11 March 2019 (UTC) * He switched back to Outlines [[Special:Contributions/The_Transhumanist]] which are another unpopular plague for Wikipedia. The assertion that hundreds of thousands of readers a month are looking at his 3500 portals is fanciful at best and not supported by readership stats. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 21:58, 11 March 2019 (UTC) :::'''Support opposing anything TTH says from now on'''. Per [[User:Only in death|OiD]]. [[User:Serial Number 54129|<span style="color:black">'''——'''</span>]][[Special:Contributions/Serial Number 54129|<span style="color:black">''SerialNumber''</span>]][[User talk:Serial Number 54129|<span style="color:#8B0000">54129</span>]] 13:30, 12 March 2019 (UTC) ::::'''Strong oppose taking ad hominem arguments into consideration'''. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 13:49, 12 March 2019 (UTC) :::::'''Oppose [[WP:BLUDGEON]]ING'''. [[User:Serial Number 54129|<span style="color:black">'''——'''</span>]][[Special:Contributions/Serial Number 54129|<span style="color:black">''SerialNumber''</span>]][[User talk:Serial Number 54129|<span style="color:#8B0000">54129</span>]] 15:48, 18 March 2019 (UTC) :*@[[User:Legacypac|Legacypac]], {{em|technically}} he's probably telling the truth. Even obvious drivel like [[Portal:Coconuts]] [https://tools.wmflabs.org/pageviews/?project=en.wikipedia.org&platform=all-access&agent=user&range=latest-60&pages=Portal:Coconuts averages around five views per day], thanks to webcrawlers and people who have the articles watchlisted and are wondering "what's this mystery link that's just been spammed onto the article I wrote?"; multiply that by 3500 and you have 500,000 pageviews per month right there.&nbsp;&#8209;&nbsp;[[User:Iridescent|Iridescent]] 22:52, 11 March 2019 (UTC) *'''Comment''' Neelix created about 50,000 redirects, which were reviewed by the community. The number of portals is an order of magnitude smaller. If X3 is to be introduced, it should involve a similar review process. We should certainly delete portals which have too narrow a scope or are of poor quality and cannot be improved. However, systematic deletion of all portals which qualify for consideration, purely on an ''[[WP:ad hominem|ad hominem]]'' argument, would be as wrong as semi-automatic creation. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 10:51, 12 March 2019 (UTC) ::Absolutely not. Look at the rate these were created [https://en.wikipedia.org/w/index.php?limit=5000&title=Special%3AContributions&contribs=user&target=The+Transhumanist&namespace=100&tagfilter=&newOnly=1&start=&end=] sometimes several dozen an hour, and sometimes an average of 12 seconds each. If so little thought went into creation, why make deletion so difficult? The Neelix cleanup took far too long (I was a big part of it) and we deleted the vast majority of those redirects anyway the extra I can edit this! hard way. As far as I could see the editors who insisted we review everything did none of the reviewing. Also, these were created in violation of [[WP:MEATBOT]] which is a blockable or at least sanctionable offense [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 11:04, 12 March 2019 (UTC) :::Two wrongs do not make a right - it is much more important that we get the cleanup right than it happens quickly. Whether or not TTH is blocked or otherwise sanctioned is completely irrelevant. While many (maybe even most) of the created portals should be deleted not all of them should be, and this needs human review: see requirement 2 for new CSD criteria at the top of [[WT:CSD]]. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 12:07, 12 March 2019 (UTC) ::: {{ping|Thryduulf|Certes|SmokeyJoe|Legacypac}} Concerning the rate, Legacypac's observation is not accurate. What the edits he is citing do not show, is the method by which the pages were created: they were created in batches, in tabs. Before saving, all the pages/tabs were inspected. For the pages that did not pass muster, such as those that displayed errors (this did not catch all errors, because lua errors can be intermittent or turn up later due to an edit in source material being transcluded), the tabs for those were closed. In a batch of 50, 20 or 30 might survive the cull (though batch sizes varied). Some tabs got additional edits in addition to inspection, to fix errors or remove the sections the errors were in, or further development. After all the tabs in a batch were inspected and the bad ones culled, the remaining ones were saved. That's why the edits' time stamps are so close together. If you look more closely, you'll see the time gap is between the batches rather than the individual page saves. Therefore, [[WP:MEATBOT]] was not violated. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 18:55, 12 March 2019 (UTC) ::::He claims [https://en.wikipedia.org/w/index.php?title=User_talk:Legacypac&diff=prev&oldid=885294281] he created 500 portals in 500 to 1000 minutes. and is using a script [[Wikipedia:Miscellany_for_deletion#User:The_Transhumanist/QuickPortal.js]] If this is not MEATBOT we should refind MEATBOT as meaningless. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 19:07, 12 March 2019 (UTC) ::::: A minute or two per portal of the new design sounds about right. Note that the script doesn't save pages. It puts them into preview mode, so that the editor can review them and work on them further before clicking on save. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 19:39, 12 March 2019 (UTC) ::::: {{replyto|Legacypac|The Transhumanist}} As I said above, the method of creation is irrelevant to this proposal, as is what (if any) sanction is appropriate. Likewise discussions of [[WP:MEATBOT]] don't affect this at all. What matters is only that these pages exist but some of them should not, this proposal needs to be rejected or modified such that it deletes only those that need deleting without also deleting those that do not. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 20:42, 12 March 2019 (UTC) *'''Procedural note''' I have advertised this discussion at [[WP:VPP]] and would encourage others to add links where they think interested editors might see. I think this should remain open for 30 days, as it is quite a significant policy change. [[User:GoldenRing|GoldenRing]] ([[User talk:GoldenRing|talk]]) 09:24, 13 March 2019 (UTC) *'''Support''' now that the MfDs ([[Wikipedia:Miscellany for deletion/Portal:Urinary system|here]], [[Wikipedia:Miscellany for deletion/Portal:Intermodal containers|here]], [[Wikipedia:Miscellany for deletion/Portal:You Am I|here]], [[Wikipedia:Miscellany for deletion/Portal:Spaghetti|here]], [[Wikipedia:Miscellany for deletion/Portal:Aleksandr Solzhenitsyn|here]], [[Wikipedia:Miscellany for deletion/Portal:Agoura Hills, California|here]], [[Wikipedia:Miscellany for deletion/Portal:English language|here]], [[Wikipedia:Miscellany for deletion/Portal:Prostitution in Canada|here]], [[Wikipedia:Miscellany for deletion/Portal:Cotingas|here]], [[Wikipedia:Miscellany for deletion/Portal:Burger King|here]] and [[Wikipedia:Miscellany for deletion/Portal:E (mathematical constant)|here]]) are closing with strong consensus around delete, it is clear this is the fastest path to improving the encyclopedia (which is what we are here for, remember?) Any argument that 3,500 more portals have to go through MfD is strictly throwing sand in the gears. It is going to be enough manual labor pulling the links to the deleted portals from all the templates and pages they have been added to. [[User:UnitedStatesian|UnitedStatesian]] ([[User talk:UnitedStatesian|talk]]) 15:15, 13 March 2019 (UTC) **That shows that ''a'' speedy deletion criterion is possibly warranted for some, but several comments on those discussions - including your own at [[Wikipedia:Miscellany for deletion/Portal:Spaghetti]] - indicate that ''this'' proposed criterion is too broad. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 15:33, 13 March 2019 (UTC) ***You misunderstand my comment at that MfD: I strongly support that portal's deletion and all the others that would be covered by this proposed criterion. [[User:UnitedStatesian|UnitedStatesian]] ([[User talk:UnitedStatesian|talk]]) 15:37, 13 March 2019 (UTC) ****You supported the deletion of Portal:Spaghetti because the topic was covered by Portal:Pasta, even though Portal:Pasta would be deleted under this criterion? That's rather disingenuous at best and very significantly and unnecessary disruptive at worst. [[Portal:Pasta]] is an example of a portal that should not be deleted without discussion. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 16:00, 13 March 2019 (UTC) *****Again, you misunderstand my reasoning: I was specifically pointing out to another editor that the existence of [[Portal:Pasta]] could NOT be a reason to delete [[Portal:Spaghetti]], since in my opinion [[Portal:Pasta]] would likely also be deleted. Instead, I think the current [[Wikipedia:Portal/Guidelines]] provide ample OTHER reasons for deleting both portals (and many, many others, of course). Hope that clarifies. [[User:UnitedStatesian|UnitedStatesian]] ([[User talk:UnitedStatesian|talk]]) 17:55, 13 March 2019 (UTC) *'''Strong oppose and keep all'''. [[WP:P2]] covers unnecessary portals, and there is no rationale presented other than [[WP:IDLI]] to delete a large proportion of all of them, which were all kept after a RfC in 2018. The next time content policies are created at AN by the cabal of admins, I am retiring from Wikipedia. <span style="background-color:#cee">[[User:Wumbolo|<span style="color:#066;font-family:Symbol">w</span><span style="color:#066;font-family:Segoe Script">umbolo</span>]]</span> [[User talk:Wumbolo|<span style="color:#37C;font-family:webdings">^^^</span>]] 16:40, 13 March 2019 (UTC) *:{{ping|Wumbolo}} Well, if it came to that, take it to [[WP:RFARB]] first. Given the past history of [[WP:FAITACCOMPLI]] and [[WP:LOCALCONSENSUS]] extremism (i.e., [[WP:FALSECONSENSUS]]) cases, I have little doubt that ArbCom would agree to take a case about a gaggle of anti-portal people [[WP:GAMING]] the consensus-formation process by inventing sweeping policy changes out of their butts in a venue few content editors pay attention to and which is clearly out-of-scope for such a decision, even if it somehow had sufficiently broad input (e.g., via [[WP:CENT]]). I'm skeptical any alleged consensus is going to come out of this discussion, anyway. <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 17:33, 14 March 2019 (UTC) *'''Support''' This is a repeat of the Neelix situation. &#8213;[[User:Susmuffin|<span style="color:#8B008B;">'''''Susmuffin'''''</span>]]&nbsp;[[User talk:Susmuffin|<sup><span style="color:#8B008B;">'''''Talk'''''</span></sup>]] 00:01, 14 March 2019 (UTC) **{{replyto|Susmuffin}} The situation has similarities, but the proposed criterion is not comparable. Criterion X1 applied only to redirects created by Neelix that the reviewing administrator reasonably believed would be snow deleted if discussed at RfD (i.e. they had to evaluate each redirect), this criterion would apply to every portal created by TTH in the timeframe without any other conditions and without the need for anyone to even look at anything other than the date of creation. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 00:13, 14 March 2019 (UTC) ***Honestly, there are far too many portals to be deleted through the usual channels. However, an quick evaluation would be reasonable, provided we keep the portal system itself. &#8213;[[User:Susmuffin|<span style="color:#8B008B;">'''''Susmuffin'''''</span>]]&nbsp;[[User talk:Susmuffin|<sup><span style="color:#8B008B;">'''''Talk'''''</span></sup>]] 00:24, 14 March 2019 (UTC) ::::Unlike Neelix who created some reasonable redirects along the way, these autogenerated portals are of uniformly low quality. The community has looked at representive samples across a variety of subject areas at MFD and the community has already deleted 143 of the 143 portals nominated at closed MfDs. The yet to be closed MfDs are headed to increasing that number. No one has suggested any alternative deletion criteria for X3. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 00:45, 14 March 2019 (UTC) :::::That nobody has suggested an alternative is irrelevant - it's not up to those who oppose this proposal to fix it, and those who support it are by-and-large ignoring the objections. The MfDs have been selected as a representative sample of those that, after review, are not worth keeping and have been reviewed by MfD participants. This does not demonstrate that deletion without review is appropriate - indeed quite the opposite. Remember there is no deadline, it is significantly more important that we get it right than we do things quickly. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 09:59, 14 March 2019 (UTC) ::::::Not particularly similar to the redirect situation that occurred; portals are vastly different in nature and composition from simple redirects. <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 03:16, 26 March 2019 (UTC) * '''Oppose''' as unwarranted and dangerous (and circular reasoning). First, we do not modify CSD without a strong community (not admins' star chamber) consensus that an entire class of material is not just categorically unwanted but so unwanted that it should be deleted on sight without any further consideration. It's our most dangerous policy, and a change like this to it should be an RfC matter at [[WP:VPPOL]]. In theory, it could be at [[WT:CSD]], except there is not yet any establishment of a consensus against these portals, and VPPOL is where that would get hashed out, since it's a project-wide question of content presentation and navigation (and maintenance, and whether tools can permissibly substitute for some manual maintenance, and ...). The cart is ahead of the horse here; we can't have a speedy deletion criterion without already having a deletion criterion to begin with. I strongly agree with SmokeyJoe: {{tq|"{{em|Oppose}} any and all notions of creating new CSD criteria at any drama board. Discussions here are too rushed, too emotive, too reactionary."}} <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 17:05, 14 March 2019 (UTC) *'''Oppose''' – [[WP:P2]] covers problematic portals just fine. A concerning issue here is that some users herein appear to simply not like portals in general, and so there are several arguments above for mass deletion as per this "I don't like it" rationale. Mass deletion should be a last step, not a first step, and portals should be considered on a case-by-case basis. <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 22:22, 14 March 2019 (UTC) ::You created some with the same tools. One or two of your creations are now at MfD which is why you are now engaging against this solution. We will consider each of your creations at MfD. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 02:34, 15 March 2019 (UTC) :::My !vote here is based upon my view of the matter at hand, and as such, it stands. Period. Regarding my portal creations, so what? You come across as having a penchant for scolding content creators on Wikipedia if you don't like the medium that is used. Please consider refraining from doing so, as it is unnecessary, and patronizing. <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 01:12, 16 March 2019 (UTC) *:{{reply to|Northamerica1000}} I agree - for example, I actually welcome the creation of [[Portal:Economics]] because I think econ should be established as distinct from business as in [[Portal:Business and economics]]. <span style="white-space: nowrap;">[[User:Qzekrom|Qzekrom]] [[User talk:Qzekrom|💬]] <sup>they</sup><sub>them</sub></span> 02:20, 18 March 2019 (UTC) *'''Oppose''' - this CSD seems have to no more objective criteria than "shoot unless someone defends it". For this to be justified, they'd have to explain how no-one reacting within 24 hours was sufficient reasoning. As far as the initial proposal included, it didn't contain any acceptable objective criteria for something warranting deletion on quality grounds. '''Far worse''', it didn't contain suitable justification (whether popularity/quality) for these portals to impose such a major hindrance to Wikipedia as to warrant a process with as few eyes (per consideration) as CSD. The nominator might have had more luck with a PortalPROD mechanism. [[User:Nosebagbear|Nosebagbear]] ([[User talk:Nosebagbear|talk]]) 23:09, 14 March 2019 (UTC) ::This CSD exactly meets each criteria for CSD's at the [[WP:CSD]] page. It is clear. It is easy to decide if the page meets the CSD. We ran 145 of these portals through MfD already and none survived. Numerous editors suggested this CSD in the Village Pump discussion. These mass created portals universally have the same flaws. Therefore this oppose rational is flawed. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 02:34, 15 March 2019 (UTC) *'''Support''': will allow to quickly manage the auto-created portals of zero utility. --[[User:K.e.coffman|K.e.coffman]] ([[User talk:K.e.coffman|talk]]) 02:23, 15 March 2019 (UTC) *'''Support enthusiastically'''. Taking all these portals through MFD would be a massive drain on community resources. [https://en.wikipedia.org/w/index.php?title=Special:Contributions&dir=prev&offset=20180912064801&limit=500&contribs=user&target=The+Transhumanist&namespace=100&tagfilter=&newOnly=1&start=&end= TTH created these portals at sustained speeds of up to 40 per hour], so even the time taken to apply a CSD tag and assess it 24 hours later will require more editorial time than TTH took to create them. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 07:23, 15 March 2019 (UTC) *'''Strong oppose''' - There are good quality portals that will be excluded, few maybe, but deserve to remain. For example [[Portal: Cities]], <s>[[Portal: Architecture]]</s> [[Portal:Sculpture]].[[User:Guilherme Burn|Guilherme Burn]] ([[User talk:Guilherme Burn|talk]]) 11:40, 15 March 2019 (UTC) **@[[User:Guilherme Burn|Guilherme Burn]], maybe those are worth keeping. Or maybe not. But even if they are good, they are not worth the price of the community committing huge amounts of time to individually debating every one of the thousands of useless portals which members of the portal project have spewed out over the last year (often as drive by creations, and which project members have then piled into MFDs to keep. ::If the Portals Project had exercised discretion so far, then we would be in a very different place. But it's utterly outraegous to ask the community to devote more time to assessing this spam than the Portal Project put into creating them. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 12:10, 15 March 2019 (UTC) :::Could these portals be marked to be spared?[[User:Guilherme Burn|Guilherme Burn]] ([[User talk:Guilherme Burn|talk]]) 13:03, 15 March 2019 (UTC) ::::{{replyto|Guilherme Burn}} not according to the proposal as written. The only chance of saving is if an admin chooses to notify and wait 24 hours ''and'' somebody objects within those 24 hours ''and'' someone spots that CSD has been declined previously if it gets renominated. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 14:01, 15 March 2019 (UTC) ::::{{yo|Guilherme Burn}} [[Portal:Cities]] is [https://tools.wmflabs.org/pageviews/?project=en.wikipedia.org&platform=all-access&agent=user&range=latest-90&pages=Portal:Cities totally moribund and unread], and has [[Portal talk:Cities|never had a single participant]]. [[Portal:Architecture]] dates from 2005 and wasn't created by TTH or this tag-team, so wouldn't be deleted regardless (although I imagine [https://en.wikipedia.org/w/index.php?title=Portal:Architecture&oldid=883812193 the enormous wall of pointless links which TTH's bot dumped onto the page a couple of months ago] would be reverted).&nbsp;&#8209;&nbsp;[[User:Iridescent|Iridescent]] 14:08, 15 March 2019 (UTC) :::::{{replyto|Iridescent|BrownHairedGirl}}One portal that does not meet Mfd criteria is enough for me to keep my opinion. [[Portal:Cities]] Although poor visualized is an important and good quality portal and the [[Portal:Sculpture]] (erroneously I quoted another portal) as well.[[User:Guilherme Burn|Guilherme Burn]] ([[User talk:Guilherme Burn|talk]]) 13:20, 20 March 2019 (UTC) ::::::{{yo|Guilherme Burn}} please can you clarify that statement that {{tq|One portal that does not meet Mfd criteria is enough for me to keep}}. :::::::Are you saying that you are willing to personally scrutinise a few thousand drive-by Portals at MfD in order to find the one which should be kept? Or do you want others to do that work? :::::::TTH as made it very clear that these portals took on average between one and two minutes each to create ([https://en.wikipedia.org/w/index.php?title=User_talk:Legacypac&diff=next&oldid=885272075] {{tq|Have you tried creating 500 portals? It is rather repetitious/tedious/time-consuming (from 500 to 1000 minutes)}}). So many multiples of that-one-to-two minutes per portal do you think it is fair to ask the community to spend scrutinisng them? And how much of that time are you prepared to give? --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 14:12, 20 March 2019 (UTC) ::::::::{{tq|So many multiples of that-one-to-two minutes per portal do you think it is fair to ask the community to spend scrutinisng them?}}Yes. The community also failed to set criteria for creating portals. What is the difference of [[Portal: Lady Gaga]] to [[Portal: ABBA]]? For me both should be excluded. If the community not had problems to create a portal for a unique singer, why now have problems with someone who has decided to create portals for lot of singers? And to be honest I do not think so much work like that, Mfd can be executed in blocks excluding several portals at the same time.[[User:Guilherme Burn|Guilherme Burn]] ([[User talk:Guilherme Burn|talk]]) 17:11, 20 March 2019 (UTC) *'''Oppose''' per SmokeyJoe et al. Completely unnecessary to override already existing procedure. '''''[[User:Paine Ellsworth|<span style="font-size:95%;color:darkblue;font-family:Segoe Script">Paine&nbsp;Ellsworth</span>]]'''''<small>,&nbsp;ed.&nbsp;&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;</small>&nbsp;<small>17:12, 15 March 2019 (UTC)</small> **{{yo|Paine Ellsworth}} the administrative work of trawling through several thousand drive-by-created micro-portals is huge. Cleaning up this flood of portalspam through MFD requires a huge amount of editorial time, vastly more than was involved in creating the spam. ::If you think that existing procedure is fine, why aren't you devoting large hunks of ''your'' time to doing the cleanup by the laborious procedure you defend? --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 02:33, 16 March 2019 (UTC) ::*Because...? I don't know, I guess I think this whole thing is rather more of a knee-jerk reaction than a brainy, measured response. Sure I've done my share of big, teejus jobs for the project and plan to continue (on my terms). I have a lot of respect for editors like yourself and TTH who've been lifting this project out of the primal soup of its beginnings even longer than I have (I went over ten in January, or was it Feb? whatever) and I'm tired of seeing good, solid editors get reamed for their work and retire, just leave or get banned. Don't think it can't happen to you, because as good as you are, neither you nor the rest of us are immune to the gang-up-on-em mentality that turns justice into vengeance 'round here. Think you should also know if you don't already that I'm about 95 farts Wikignome and 5 parts other, and it takes a lot less for us to think we're being badgered and handled. I voted correctly for me and my perceptions, and I don't expect either of us will change this unwise world one iota if you vote you and yours''!'' WTF ever happened to forgiveness? REspectfully, '''''[[User:Paine Ellsworth|<span style="font-size:95%;color:darkblue;font-family:Segoe Script">Paine&nbsp;Ellsworth</span>]]'''''<small>,&nbsp;ed.&nbsp;&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;</small>&nbsp;<small>13:28, 16 March 2019 (UTC)</small> :::*{{re|Paine Ellsworth}} Thank you for adding the words that I dared not write in case I was next against the wall. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 16:31, 16 March 2019 (UTC) ::::*{{re|Paine Ellsworth|Certes}} I genuinely don't in any way doubt the good faith of either of you. :::::But it seems to me that the unintended effect of what you are both saying is something like "I am not making any effort to assist the cleanup of this mass portalspam, but I will take the effort to oppose measures which reduce the huge burden on those who are actually doing that necessary cleanup work". :::::As I say, I do not believe that is what either of you ''intend''. But all I see from either of you is opposition to any restraint on the portalspammer, and opposition to anything which would assist the cleanup. I respect the fine principles from which you two start, but I urge you to consider the effects on the community both of not easing the cleanup burden and of continuing to describe the likes of TTH in positive terms. Look for example at my post in a thread above about the [[#Lack_of_good_faith_from_User:The_Transhumanist]], and at Iridiscent's observation above that of TTH's previous history of spamming useless pages. :::::As to {{tq|lifting this project out of the primal soup of its beginnings}} ... that's an extraordinary way to describe TTH's spamming of hundreds, if not thousands, of useless, unfinished micro-portals. {{smiley|sad}} --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 22:53, 16 March 2019 (UTC) ::::::I am not making any effort to assist with mass deletions, beyond !voting to delete the clearer cases. We already have enough enthusiasts working in that department. Until recently, I had been adjusting individual portals and enhancing the modules behind them to improve quality, but I slowed down when it became obvious that my contributions in that area will be deleted. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 00:19, 17 March 2019 (UTC) :::::::So that's as I feared, @[[User:Certes|Certes]]: members of that WikiProject are leaving it to others to clean up the mess created by the WikiProject and its members. ::::::: That only reinforces my impression of a collectively irresponsible project, which doesn't restrain or even actively discourage portalspam, doesn't try to identify it, and doesn't assist in its cleanup. ::::::: That's a marked contrast with well-run projects. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 02:06, 17 March 2019 (UTC) ::::::::{{to|BrownHairedGirl|label1=BHG}} not a surprising perspective, possibly a hasty generalization, however that's not your worst move. Your worst move is to consider "mass deletions" of what you deem "portalspam" as {{du|better}} than the "mass creations" of portals. Who's really to say? As an editor mentions below, "...these portals are doing no harm so great that they can be deleted without due process." So maybe you're wrong about those mass deletions that portray some portals as WMDs instead of the harmless windows into ''Wikipedia'' that they were meant to be? No matter, at present you are part of the strong throng. If you're right, you're right. But what if you and the strong throng are wrong? May things continue to go well with you''!'' '''''[[User:Paine Ellsworth|<span style="font-size:95%;color:darkblue;font-family:Segoe Script">Paine&nbsp;Ellsworth</span>]]'''''<small>,&nbsp;ed.&nbsp;&nbsp;[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;</small>&nbsp;<small>07:01, 17 March 2019 (UTC)</small> *'''Support''' and also apply it to those created by Northamerica1000, who has made such useless portals as [[Portal:Strawberries]] and [[Portal:Waffles]]. [[User:Reywas92|Reywas92]]<sup>[[User talk:Reywas92|'''Talk''']]</sup> 08:26, 16 March 2019 (UTC) **{{replyto|Reywas92}} [[user:Northamerica1000|Northamerica1000]] has created only 70 pages in the portal namespace (excluding redirects) in the relevant timeperiod. In no conceivable scenario does that justify a speedy deletion criterion. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 11:58, 16 March 2019 (UTC) *'''Support''' per F&K (whatever course of action that will result in every portal created in this manner being deleted with the minimal of time and effort required) and SN (nuke from orbit). I'll be honest I don't know enough to know whether it should be a X3 or a P2 or a single MfD list with 4,500 entries... but it should '''not''' need to involve manually tagging pages that were created by a bot or otherwise spending any real time figuring out which should be kept and which should not be kept. Delete them all. If editors feel like this portal or that portal should be kept, let them make the case for undeletion afterwards which can be examined on a case-by-case basis. (If that process is followed, it goes without saying that the portal creator should be banned from making any such undeletion requests.) [[User:Levivich|Leviv]]&thinsp;<span style="display:inline-block;transform:rotate(45deg);position:relative;bottom:-.57em;">[[User talk:Levivich|ich]]</span> 17:25, 16 March 2019 (UTC) *:How are we supposed to work out what is worth undeleting, short of downloading all portals in advance lest they be deleted? [[User:Certes|Certes]] ([[User talk:Certes|talk]]) *::If an editor is not aware of a portal existing, then that editor shouldn't be asking for it to be kept. If there are particular portals that editors know they want saved, then they should have an opportunity to request that it be saved. But there should be no one-by-one examination of thousands and thousands of portals created by one user using semi-automatic methods. [[User:Levivich|Leviv]]&thinsp;<span style="display:inline-block;transform:rotate(45deg);position:relative;bottom:-.57em;">[[User talk:Levivich|ich]]</span> 19:39, 16 March 2019 (UTC) *:Kill them all and let God sort them out is very much ''not'' the way Wikipedia works and is very much ''not'' the way it should work. Why should the review be restricted to administrators (as your proposal would require)? Why is it preferable to significantly harm the encyclopaedia by deleting good portals than to do the job properly and delete only those that actually need deleting (which are doing significantly less harm by existing than deleting good ones would cause)? [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 18:06, 16 March 2019 (UTC) *::So let me create several thousand pages semi-automatically, and then I'll put it to you to go through them one by one and tell me which should be deleted and why? I don't think that's how it should work. It should work in reverse. The default should be delete them all, with some process for allowing people to request that particular portals not be deleted. BTW, when I say "all portals" I mean all portals covered by this proposal, not all portals that exist on Wikipedia. [[User:Levivich|Leviv]]&thinsp;<span style="display:inline-block;transform:rotate(45deg);position:relative;bottom:-.57em;">[[User talk:Levivich|ich]]</span> 19:39, 16 March 2019 (UTC) *:::If an editor created several thousand pages semi-automatically, the correct sequence of events is to analyse a representative sample to determine whether ''consensus'' is that they are (a) all good, (b) mostly good, (c) all bad, (d) mostly bad, or (e) a mixture. If (a) then no action is necessary, if (b) then individual deletion nominations are the correct response. If (c) then a CSD criterion to remove all of them is appropriate, if (d) or (e) then a CSD affectingly only the bad ones should be explored. In this the situation is somewhere between (d) and (e) depending on your point of view, but this proposal is treating them as (c). As I've said several times, I'm not opposed to a criterion proposed (in the right place) that caught only the bad ones and allowed for objections - that is not this proposal. This situation is frequently compared to Neelix, but the proposal is very different - this one: All pages created between Time A and Time B, unless anyone objects to the optional tagging within 24 hours. Neelix: All pages created between Time A and Time B that would be snow deleted if nominated at RfD, retargetting would not lead to a useful redirect and no other editor has materially edited the redirect. Do you now understand the fundamental difference? Also remember that pages can be tagged by bot. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 20:56, 16 March 2019 (UTC) *::::Yes. We also need to clarify one important detail of the proposal: would an editor be required to look at the portal before applying CSD, or is there an assumption that everything created by this editor in that time period is automatically rubbish and does not deserve assessment? [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 22:29, 16 March 2019 (UTC) *:::::If a human being didn't spend a lot of time making a page, then human beings should not spend much time deciding whether to keep it. I put it to you again: suppose tomorrow I create 5,000 new pages and ask you to go through them and decide which to keep and which to delete. That would be insane; this is a website of volunteers; my doing such a thing would be disruptive. It would make work for others. ''Nobody'' reading this thinks it would be a good idea for me to do such a thing. Yet this is what is essentially being asked of us. Insofar as I have a !vote, I !vote no. Delete them all. They are all bad. Any that are good can be recreated as easily as they were created in the first place. Letting people flag keepers in one way or another is a perfectly reasonable way to prevent the baby from being thrown out with the bathwater. But yes, my starting point is that all of them should be deleted because none of them should have been made in the first place, and they do not have content value. Some portals are the product of careful creation and extensive work, but not 5,000 or however-many automatically created by one editor. The quantum portal idea is a much better idea, anyway. [[User:Levivich|Leviv]]&thinsp;<span style="display:inline-block;transform:rotate(45deg);position:relative;bottom:-.57em;">[[User talk:Levivich|ich]]</span> 02:38, 17 March 2019 (UTC) *::::::I've alreadyanswered this immediately above, but as you apparently don't like the answer I'll respond again. If you create 5000 new pages in good faith (which TTH did), then the correct response is for others to go through and look at a representative sample, then gain a consensus about whether they are all bad, mostly bad, a mixture, mostly good or all good. This has been done with TTH's portals and while you may think they are all bad that is not the consensus view, especially as others have taken over some and either have improved them or are working on improving them. This means that it is important that only the bad ones are deleted meaning any proposal (such as this one) to delete all of them is overbroad and needs to be opposed. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 10:03, 17 March 2019 (UTC) *::::::::This statement by Thryduulf is incorrect on many levels. Who has taken over and improved any of his creations? Where is the concensus view that they are not all bad when so far zero of his creations have been kept at MfDs. Where is the proof any of this was in good faith when he admits several sections down that no one (including him) has followed [[WP:POG]] [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 10:38, 17 March 2019 (UTC) *:::::::::Are you even reading the comments made by those who disagree with you because I'm not seeing evidence of it, especially when it comes to the MfDs (to reiterate, a reviewed selection of the worst pages being deleted by consensus but not unanimously in all cases does not provide evidence of the need for deletion of all of them without possibility of review). [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 16:36, 17 March 2019 (UTC) *:::::::{{u|Thryduulf}}, so I spend less than 1 minute per page creating 5,000 pages; ''you'' and others spend–what, an hour, cumulatively, at least?–per page to analyze it, discuss it, vote it, close it, and delete it. I spend 5,000 minutes; the community spends 5,000 hours. With all due respect I am flabbergasted to hear such a high-ranked Wikipedian express the view that this is OK or preferred. Even with your representative sample approach, say it's 100 portals that are looked at, that's still 100 hours of labor forced upon volunteers. In my opinion, no one should be allowed to make 5,000 pages without going through something like a BAG process to seek community approval. There was once a time, years ago, when it made sense to, for example, automatically create a stub for every known city and town in the world. I believe that time has long since passed; there are not 5,000 pages that can be created automatically that we need to have that we do not already have (IMO). And as for consensus, if they're not being kept at MfD, the consensus is clear. Those portals that people maintain manually are the same ones that can be flagged as exceptions to a mass-deletion. So I feel like we're on the same page about consensus, but I'm saying the consensus to keep a particular portal can be effectuated by allowing people to flag them as exceptions to mass deletion, whereas you seem to be suggesting: let's get together and spend an hour per portal to decide if it should be kept, even though nobody spent anywhere near that time creating it in the first place. If that's where we are, we'll have to agree to disagree, because I fundamentally don't believe these portals are worth a one-by-one analysis, and I believe the representative sample approach you advocate has been done and has led to the conclusion that these are worth mass deleting with exceptions. I guess that's for a closer to make the ultimate decision about, but for my part, from ''uninvolved'' editors, I'm seeing a lot more support than oppose for mass deletion. [[User:Levivich|Leviv]]&thinsp;<span style="display:inline-block;transform:rotate(45deg);position:relative;bottom:-.57em;">[[User talk:Levivich|ich]]</span> 14:42, 17 March 2019 (UTC) *::::::::{{replyto|Levivich}} If you're just going to ignore all the explanations I give in response to you (twice) and all the explanations elsewhere from me and others about why a reviewed selection of the worst being deleted (and not unanimously in all cases) is not evidence of the need for all of them to be deleted without possibility of review by others then it is clear we will never agree. Fortunately, per [[WP:VOLUNTEER]], nobody is being forced to do anything they don't want to do - including you - and it's really disappointing that someone as experienced as you feels the need to ''prevent'' that work being done by others just because you don't want to. Perhaps between now and the time this is closed those in support of this overbroad proposal will actually choose to address the points in opposition but unless they do the only possible outcomes are no consensus or consensus against. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 16:36, 17 March 2019 (UTC) *:::::::::{{u|Thryduulf}}, I heard you say: pick a representative sample and decide if they're all bad, some bad, etc. As I understand it, a representative sample has been sent to MfD with consensus to delete almost all of them, if not all of them (I'm not sure if lists I've seen are complete). Then you say that just because the sample is all-delete doesn't mean the whole category is all-delete. I infer you think the sample is not well-chosen? By TTH's admission there are like 4,500–5,000 portals, and a tiny tiny percentage of those are being manually maintained–like less than 5%. Are we on the same page about the facts so far? If so, where do you see consensus other than "delete 95% of these things"? Why can't we tag the 100 that are manually maintained and delete the remaining 4,500? I am reading what you're writing, but I am not understanding it. [[User:Levivich|Leviv]]&thinsp;<span style="display:inline-block;transform:rotate(45deg);position:relative;bottom:-.57em;">[[User talk:Levivich|ich]]</span> 16:44, 17 March 2019 (UTC) *'''Support''': these portals are easy to create semi-automatedly and contain no information not found in articles so we're not losing any information from Wikipedia, which sets this apart from most other CSD criteria. An alternative proposal I would support is to expand the remit of P2 to apply to any portals with fewer than one-hundred pages under their scope (or alternatively, fewer than one-hundred notable topics if there is evidence that the portal creators and users are planning to create such topics as articles). If a topic doesn't have 100 pages on it at the bare minimum, there's absolutely no reason to focus a portal around it. Even for portals covering tens of thousands of articles, reader interest is very, very low and the current semi-automated busywork is not serving the readers. <span class="nowrap">— '''[[User:Bilorv|Bilorv]]''' (he/him) <sub>[[User talk:Bilorv|('''talk''')]]</sub></span> 19:05, 16 March 2019 (UTC) **{{replyto|Biorv}} a proposal for expansion of speedy deletion criterion P2 is being discussed currently at [[Wikipedia talk:Criteria for speedy deletion}} (which is where proposals related to speedy deletion criteria ''should'' be held, not AN), so I will refrain from explaining here why I oppose your suggestion to avoid splitting the discussion. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) *'''Support with exceptions'''. I support the speedy deletion of all portals auto-created in recent months as it seems excessive and unnecessary. However, those few portals which are manually maintained in good faith should be kept. Down the line we need to take another look at a notability threshold to keep a lid on portalmania. [[User:Bermicourt|Bermicourt]] ([[User talk:Bermicourt|talk]]) 22:37, 16 March 2019 (UTC) **If you believe there should be exceptions for portals maintained in good faith (and I agree there should be), then you should be opposing this proposal in favour of an alternative one that allows for that. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 22:59, 16 March 2019 (UTC) *::X3 only covers the mass created automated portals started by TTH so already excludes the type of portal [[User:Bermicourt]] wants to exclude. Thryduulf is muddying the facts. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 23:08, 16 March 2019 (UTC) *'''Oppose''' because a) on procedural grounds this shouldn't be discussed at the AN "closed shop" and b) because these portals are doing no harm so great that they can be deleted without due process. It is not TTH's fault that the guidelines for portal creation are permissive. [[User:Triptothecottage|Triptothecottage]] ([[User talk:Triptothecottage|talk]]) 02:56, 17 March 2019 (UTC) *'''Comment''': <s>I have already voted here but</s> I just wanted to provide an example of how much thought was going into the creation of these portals. [[Portal:Aquatic ecosystem]] was created by TTH on Aug 15 2018 and in classified as "Complete" despite having 4 selected images. An identical portal was created at [[Portal:Aquatic ecosystems]] by TTH on Nov 24 and is classified as "Substantial" (the portalspace equivalent of B-class). One wonders, which portal is of better quality, how was this determined, and how was this oversight not caught? —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua">&nbsp;python</span><span style="color:#ff0">coder&nbsp;</span></span>]] ([[User talk:pythoncoder|talk]]&nbsp;&#124;&nbsp;[[Special:Contribs/pythoncoder|contribs]]) 13:33, 17 March 2019 (UTC) *'''Oppose''': Criteria are supposed to be '''uncontestable''' - almost all pages could be deleted under this criterion, according to consensus. Looking at the [https://en.wikipedia.org/w/index.php?limit=50&title=Special%3AContributions&contribs=user&target=The+Transhumanist&namespace=100&tagfilter=&newOnly=1&start=&end= most recent 50 portals created by TTH], I see a lot of frivolous ones, but I also see [[Portal:Pumpkins]], [[Portal:Woodpeckers]], [[Portal:International trade]], and [[Portal:World economy]], all of which represent subjects with well-populated categories. And I could add at least as many that are debatable. If TTH, now under a topic ban, were to create more portals, they could be speedy deleted under [[WP:G4]]. But the pages considered here were created before the ban, so they should stand or fall on their own merits. <span style="font-family:Comic Sans MS; color:grey;">[[User:RockMagnetist|RockMagnetist]]([[User talk:RockMagnetist|talk]])</span> 06:05, 18 March 2019 (UTC) **{{replyto|RockMagnetist}} I think you mean [[WP:CSD#G5]] (Creations by banned or blocked users) rather than [[WP:CSD#G4]] (Recreation of a page that was deleted per a deletion discussion). [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 14:49, 18 March 2019 (UTC) ***[[WP:CSD#G5]] cannot be used here. The locus of G5 revolves around obliterating the edits of LTA's and sockpupeters and for ban-evasion in a generalized scope.&nbsp;<b style="font-family:monospace;"><< [[User:FR30799386|FR]] <sup>([[User:FR30799386/undo|mobileUndo]])</sup></b> 15:12, 18 March 2019 (UTC) ****G5 can be used to delete pages created in violation of a topic ban, if deletion is the best course of action. I would never use G5 on a page that was a borderline violation, but that's not relevant here (I can't think of any page creation that would be anything other than clear-cut one way or the other). It's all theoretical though as TTH hasn't created any pages in violation of his ban and I think it unlikely they will. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 15:37, 18 March 2019 (UTC) ****{{Reply to|FR30799386|Thryduulf}} My point in mentioning G4 (oops - G5!) was that it is a more appropriate standard for deleting pages based on who created them. The current proposal is too broad. <span style="font-family:Comic Sans MS; color:grey;">[[User:RockMagnetist|RockMagnetist]]([[User talk:RockMagnetist|talk]])</span> 16:09, 18 March 2019 (UTC) *<s>'''Oppose''' I have gone over many of the portals. It seems that there are a mix of topics which are mainstream and some which should not have been created. This isn't a white or a black issue, the wheat must be carefully separated from the chaff.&nbsp;<b style="font-family:monospace;"><< [[User:FR30799386|FR]] <sup>([[User:FR30799386/undo|mobileUndo]])</sup></b> 12:04, 18 March 2019 (UTC)</s> <small>!vote from sockpuppet struck. [[User:GoldenRing|GoldenRing]] ([[User talk:GoldenRing|talk]]) 10:17, 4 April 2019 (UTC)</small> :*FR, is there some issue with deleting them without prejudice to re-creating existing ones? These were basically made by a bot in what amounts to a single spasm, so deleting them all could be seen as a BRD reversion. The next step would be to let uninvolved editors recreate any worth keeping. Yes, that might take a while. There is no deadline and if some potentially useful portals have gone uncreated up til now, it's fine if they stay absent a little longer. [[Special:Contributions/173.228.123.166|173.228.123.166]] ([[User talk:173.228.123.166|talk]]) 04:07, 19 March 2019 (UTC) *'''Oppose''' - the proposal assumes that none of the portals should have been created, and that is an incorrect assumption. Certainly the are some that perhaps should not exist, but equally there are some that definitely should, and some that need a bit of discussion to determine consensus. Speedy deletion is not the way to resolve this. [[User:Waggers|<b style="color:#98F">W</b><b style="color:#97E">a</b><b style="color:#86D">g</b><b style="color:#75C">ge</b><b style="color:#83C">r</b><b style="color:#728">s</b>]][[User talk:Waggers|<small style="color:#080">''TALK''</small>]] 16:45, 18 March 2019 (UTC) **No, the proposal assumes (correctly) that 95% should never have been created, and that the tiny amount of time spent on those few that might be worth keeping doesn't justify the hours needed to discuss them all at MfD. The ones that get speedy deleted and would be an acceptable portal anyway can easily be recreated if someone really wants them. No effort has gone into creating these portals (usually not even the effort of checking if the result was errorfree, never mind informative or not a duplicate of existing portals), so demanding a week-long discussion for all of them because sometimes the mindless effort created an acceptable result is putting the cart before the horse. [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 08:28, 19 March 2019 (UTC) *::Throwing out the baby with the bathwater is not a sensible solution. Also, given [[WP:PAPER]], could you explain why the existence of these portals is such a problem? This is nothing more than a massive exercise in punishing a user for the crime of trying to improve the encyclopaedia and getting a bit overenthusiastic. It's horrible to see and I honestly thought the Wikipedia community was better than this. [[User:Waggers|<b style="color:#98F">W</b><b style="color:#97E">a</b><b style="color:#86D">g</b><b style="color:#75C">ge</b><b style="color:#83C">r</b><b style="color:#728">s</b>]][[User talk:Waggers|<small style="color:#080">''TALK''</small>]] 11:42, 2 April 2019 (UTC) *:::The existence of these portals is a problem because they add extra clutter to already link-intensive articles (the lower part of our articles has become more and more overcrowded over the years, with authority links, navboxes, links to sister projects, ...) and removing links with no or very little value makes the articles better and avoids sending readers to utterly useless pages created in a completely mindless manner without oversight or care. Deleting pages which are useless is not "punishing a user", that is a [[WP:OWN]] approach you show there which should not be taken into consideration when debating whether to keep or delete pages. Punishing the user would be blocking or banning them. [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 12:10, 2 April 2019 (UTC) *::::And that is happening (or has happened) too, so my point very much stands. Describing an editor's good faith hard work as "useless" isn't exactly conducive to a civil discussion either. Certainly some of the portals created are worthy of deletion, others are worthy of being kept. I could support a new PROD criterion, but CSD is not the right tool for this job. [[User:Waggers|<b style="color:#98F">W</b><b style="color:#97E">a</b><b style="color:#86D">g</b><b style="color:#75C">ge</b><b style="color:#83C">r</b><b style="color:#728">s</b>]][[User talk:Waggers|<small style="color:#080">''TALK''</small>]] 12:51, 3 April 2019 (UTC) *'''Oppose'''—CSD is for stuff where there's zero grey area. At best, this should be a specialized PROD. [[User:Gaelan|Gaelan]] [[User_talk:Gaelan|💬]][[Special:Contributions/Gaelan|✏️]] 14:42, 20 March 2019 (UTC) ====Arbitrary break (CSD criterion X3)==== *'''Oppose''' Although the vast majority may not be needed: that does not mean they should just be deleted (without oversight or consensus). The arguments ''for'' this critera seem to be centered around: 'so little work was put into them, therefore we shouldn't need to put in any work to fix it'. Why not just let them sit there then? Is there a deadline? Seeing as portals ''themselves'' are an auxiliary aide to our main focus (of writing articles) this seems unnecessary. I'm surprised that this is (at least) the second time that a [[Private Bill]] has been proposed for the cSd, I guess times have changed a bit. It seems uncollegial to respond to opposers by saying: "then you better help out with all the MfD's'. I agree with the points made by SMcCandlish and RockMagnetist among others. [[User: Crazynas|Crazynas]]<sup> [[User_talk:Crazynas|t]]</sup> 23:54, 20 March 2019 (UTC) **"Why not just let them sit there then"? Have you actually looked at the pure drivel many of these portals are? Most of these portals are not an "auxiliary aid", they are random shit, bot generated without bot permission but without actual human oversight. Sending ''any'' reader to such total shit is a disgrace. The below image is how one of these portals looks ''right now'', after it has existed for 7 months and after this discussion highlighting many problems has run for a month. Time spent discussing these (time spent looking at these) is time wasted. Any portal which people think is necessary after all can be recreated (in a much better fashion) afterwards, the speedy deletion of these doesn't restrict this. But keeping the shit an editor mass produced because their may be some less shitty pages included is doing a disservice to the people who actually wander to these portals and can only stare in dsbelief at what we show them. "'Calamba, officially the ', (Tagalog: Lungsod ng Calamba), or known simply as Calamba City is a class [[of the Philippines|of the Philippines]] in the province of , . According to the ?, it has a population of people. " [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 09:16, 21 March 2019 (UTC) :::{{small|have you looked at all the shit that sits in the mainspace (some of it for ''years'')? There are like 182,000 unreferenced articles live right now, but this is the hill we're choosing to die on? [[User: Crazynas|Crazynas]]<sup> [[User_talk:Crazynas|t]]</sup> 21:57, 21 March 2019 (UTC)}} {{collapse-top|Large in-line image}} [[File:PortalShit.png|1200px|center|Typical example of the kind of portals spammed across enwiki. Not just the five errors, but also the actual "text" of the lead article...]] {{collapse-bottom}} :::Thank you for identifying a problem with a small number of Philippines portals where the lead contains {{tl|PH wikidata}}, a technique designed for use in infoboxes. I'll pass your helpful comments on to the relevant WikiProject. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 11:02, 21 March 2019 (UTC) ::::No, please pass my comment on to all people supporting these portals, but not bothering to actually look at what they propose or defend. Creating and supporting pages with such blatant problems is basically the same as vandalism. There are e.g. also quite a few portals which confront their readers with the below "selected article" (as the default selected article, not even when scrolling deeper). Or with the same image two or three times. Or... The list of problems with these portals is near endless (selected categories only consisting of one redlink? Sure...). The fact that adding a category can cause a page to look completely different and generate different errors (like in the example above) should be a major indicator that this system, used on thousands of pages, is not as foolproof and low in maintenance as is being claimed. [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 11:36, 21 March 2019 (UTC) {{collapse-top|Large in-line image}} [[File:PortalShit2.png|400px|center|Read more... and weep]] {{collapse-bottom}} :::::Thank you for your continued help in identifying portal issues. I have found and fixed three pages which had repeated "Read more" links. If you could be kind enough to reveal which portal you have depicted as "PortalShit2.png", we may also be able to fix that case and any similar ones. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 12:20, 21 March 2019 (UTC) ::::::There are two very simple solutions: either support X3, and all these portals are instantly fixed. Or actually take a look at all these low maintenance, automatic portals of the future, find the many issues, and fix them. Which still won't solve the problem that many of them are utterly ''pointless'', mindless creations of course. I've noted more than enough problems with these portals to wholeheartedly support speedy deletion, since spending any time "corecting" a portal like the Calamba one is a waste of time (as it should be deleted anyway, speedy or not). [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 12:42, 21 March 2019 (UTC) :::::::{{replyto|Fram}} You are clearly not understanding the opposition to this proposal. It is not about supporting the inclusion of poor content, it is about opposing a speedy deletion criterion that fails the [[Wikipedia talk:Criteria for speedy deletion/Header|criteria for new and expanded criteria]] and would delete content that should not be deleted in addition to content that should. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 13:41, 21 March 2019 (UTC) ::::::::No, I often have trouyble understanding burocratic opposition which creates tons of extra work for very little actual benefit. Furthermore, I'm not convinced that this actually fails the four criteria: it is objective and nonredundant (I guess we all agree on these two?), it is frequent (in the sense that having 3K portals at MfD is quite a heavy load, it's not just one or two pages), so we are left with "Uncontestable", which doesn't mean that as soon ass someone opposes it, it becomes contested, but that "almost all pages that could be deleted using the rule, should be deleted, according to consensus.". Looking at this discussion and the MfDs, I believe this to be true. Opposing this new CSD rule "because it is contested" is circular reasoning, as you are then basically saying "it is contested because it is contested", which is obviously not a valid argument. Having a significant number of portals which fall under the X3 but should not be deleted (which doesn't equal "should never exist", only "should not exist in the current form or any older form in the page history") would be a good argument, but I haven't seen any indication of such. [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 13:57, 21 March 2019 (UTC) :::::::::{{ec}} Frequent is not an issue (it wouldn't be as a permanent criteria, but as a temporary one it's fine), non-redundant is not an issue for most (although a few might be caught by P2 that's not a significant proportion so not a probelm). This proposal (unlike the ones being discussed at [[WT:CSD]]) is objective as written (created by a single user within a defined time period). Uncontestable however very much is, the requirement is "It must be the case that almost all pages that could be deleted using the rule, should be deleted, according to consensus. CSD criteria should cover only situations where there is a strong precedent for deletion. Remember that a rule may be used in a way you don't expect, unless you word it carefully." It is very clear from this discussion and others around these portals that not all of them ''should'' be deleted - several have received strong objections to deletion at MfD, some are argued to be kept and others merged. "it is contested because it is contested" is exactly the point of this requirement - nobody argues in good faith against deleting copyright violations, patent nonsense, recreations, or specific types of articles that don't assert importance. There is consensus that were these to be discussed they would be unanimously deleted every time. There is no such consensus about these portals. Some, perhaps most, should be deleted but not all of them. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 15:47, 21 March 2019 (UTC) :::::I am pleased to report that a recent module change should eliminate the problem where articles too short to be worth featuring occasionally appear as "Read more... Read more...". This should fix the mystery portal depicted above next time it is purged. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 11:26, 22 March 2019 (UTC) :::[[User:Thryduulf]] your opposition to X3 is baffling. You oppose it basically because some topics where Portals were mass created using automated tools against policy may warrant portals. But none of these pages have any original content to preserve. They are mindless spam poorly repackaging existing content. Kind of a poor Wikipedia mirror effort. MFDing these has proven they are unwelcome - yet you want to force us to spend a week debating pages that the creator spent seconds to create without even checking for compliance against their own criteria or for major errors? If these deletions were actually controversial (the only one of the 4 CSD criteria you say is not followed) we would expect a significant number of the MfDs to close Keep. We might expect the creator to defend and explain, but instead the creator freely admits he ignored [[WP:POG]]. Seriously makes me doubt your competence and judgement. Admins should show better judgement then this. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 17:12, 21 March 2019 (UTC) ::::{{replyto|Legacypac}} Assuming you mean X3, then I have explained every single one of my reasons several times and you have either not listened or not understood on every single one of those occasions so I Will not waste even more of my time explaining them again. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 17:16, 21 March 2019 (UTC) :::Second {{u|Legacypac}}. Additionally, part of what I meant by "some might be worth keeping" is that they can be deleted, but if any were actually worthy they could be recreated, perhaps with more care and effort than this. [[User:SemiHypercube|<b style="color:#090">Semi</b>]][[User talk:SemiHypercube|<i style="color:#099">Hyper</i>]][[Special:Contributions/SemiHypercube|<u style="color:#009">cube</u>]] 17:19, 21 March 2019 (UTC) :* It seems like a lot of what is objected to can be covered by a judicious use of P2, G1, and A3 (via P1) but there's probably something I'm missing. {{yo|Fram}}, I'm not here to support bad content, but bad policy (and precedent) can be far more harmful to the project than 'repackaged nonsense' existing for a bit longer than some people want it to. This would have the side effect of saving the portals worth saving. [[User: Crazynas|Crazynas]]<sup> [[User_talk:Crazynas|t]]</sup> 22:07, 21 March 2019 (UTC) *'''Oppose''' let's discuss deletion based on content and merit of individual portals. No need to throw the baby out with the bath water, this is not how we do things here. You're proposing deletion of many very good portals here. [[User:Ɱ|<span style="text-shadow:#bbb 0.1em 0.1em 0.1em;" class="texhtml">'''ɱ'''</span>]] [[User talk:Ɱ|(talk)]] 15:41, 21 March 2019 (UTC) ::Please identify 35 out of the 3500 (1%) that are "very good portals" so we can run them through MFD to test your statement. Also there is no baby - there is no original content at all. No work done by humans is lost with X3 deletions because they were created using an automated script that was used without BAG approval to repackage existing content. Therefore [[WP:PRESERVE]] is not an issue. If someone started creating thousands of articles called "Foo lite" that just copied Foo mindlessly we would CSD them without debate. These are just in another mainspace but they are really Foo lite. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 17:12, 21 March 2019 (UTC) :::Except that's not comparable at all. The ''point'' of portals (which the community has repeatedly endorsed) is to duplicate article content and provide links to related content - which is exactly what these portals are doing. They might be doing it poorly in many cases, but that's qualitatively different to one article duplicating another. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 18:10, 21 March 2019 (UTC) ::::Wouldn't it be faster to delete them all and then recreate the ones that need recreating, rather than go through them one by one to see which to keep? Because the number of "keeps" is like 5% or 10% and not 50%? (It would have to be 50% to be equal time between the two approaches.) If you're not convinced that it's 5-10% keep and not 50% keep, what sort of representative sampling process can we engage in to test the theory? [[User:Levivich|Leviv]]&thinsp;<span style="display:inline-block;transform:rotate(45deg);position:relative;bottom:-.57em;">[[User talk:Levivich|ich]]</span> 19:13, 21 March 2019 (UTC) :::::Yes it would be faster, but [[WP:DEADLINE|there is no deadline]] so it is very significantly more important to get it right than it is to do it quickly. Deleting something that doesn't need deleting is one of the most harmful things that an administrator can do - and speedily deleting it is an order of magnitude more so. As only administrators can see pages once they have been deleted, and doing so is much harder, deleting it first makes the job of finding the good portals very significantly harder. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 21:30, 21 March 2019 (UTC) ::::::Timing matters because this issue is being discussed in several forums at once. If the first debate to close decides to delete, the portals may be gone by the time another discussion reaches a consensus to keep them. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 21:48, 21 March 2019 (UTC) *'''Comment'''I listed The Transhumanist's portal creations, latest first, and examined the top entry on each page, i.e. every 100th portal. {{cot|Assessment of a sample of TTH's recent creations}} #[[Portal:Polar exploration]] – decent appearance; no obvious errors. 50 excerpts with more links at the bottom. Four other images, plus plenty more in the 50 leads. Manual input: refining the search criteria for Did You Know and In the News (DYK+ITN). #[[Portal:Nick Jr.]] – Lua error: No images found. (To be fair, there may have been images before a recently requested module change to suppress images without captions.) 13 excerpts. No manual input: the wikitext matches that generated by {{tl|bpsp6}}. #[[Portal:Alternative metal]] – decent appearance; no obvious errors but a narrow scope. 11 excerpts; one other image. Manual input: refining DYK+ITN. #[[Portal:Modulation]] – decent but minimal portal with no obvious errors. 30 excerpts; four other images. Several manual improvements. #[[Portal:Spanish Civil War]] – potentially good portal but with a couple of display errors which look fixable. 30 excerpts; 20 other images. Manual input: routine maintenance, probably of a routine technical nature rather than creative. #[[Portal:Carl Jung]] – decent appearance; no obvious errors. 40+ excerpts; six other images. Routine maintenance. #[[Portal:Reba McEntire]] – decent appearance; no obvious errors but a narrow scope. 40+ other excerpts; six images. Routine maintenance. #[[Portal:Romantic music]] – decent appearance; no obvious errors. 40+ excerpts; two other images. Routine maintenance. #[[Portal:Anton Chekhov]] – decent appearance; no obvious errors. 36 excerpts; 17 other images. Routine maintenance. #[[Portal:Media manipulation]] – decent appearance; no obvious errors but a narrow scope. 40+ excerpts; no image section. Routine maintenance. #[[Portal:Desalination]] – decent appearance; no obvious errors but a narrow scope. 15 excerpts; six other images. Manual input: refining DYK+ITN. #[[Portal:Abuse]] – This portal has display errors which make it hard to evaluate properly. It's had plenty of manual input, possibly in attempts to fix it. #[[Portal:Emmy Awards]] – decent appearance; <strike>one minor display error which looks fixable.</strike><small>(fixed)</small> 50 excerpts; two other images. Routine maintenance. #[[Portal:Shanghai cuisine]] – decent appearance; no obvious errors but a narrow scope. 19 excerpts; four other images. Routine maintenance. #[[Portal:Saab Automobile]] – decent appearance; no obvious errors but a narrow scope. 40+ excerpts; 14 other images. Routine maintenance. #[[Portal:High-speed rail]] – decent appearance; <strike>one minor display error which looks fixable.</strike><small>(fixed)</small> 40+ excerpts; 30+ other images. Routine maintenance. #[[Portal:Tetris]] – decent appearance; no obvious errors but a narrow scope. 30+ excerpts; two other images. Routine maintenance. #[[Portal:Azores]] – decent appearance; no obvious errors but a narrow scope. 20 excerpts; 18 other images. Some manual improvements. #[[Portal:Musical instruments]] – decent appearance; no obvious errors. 40+ excerpts; 13 other images. Routine maintenance. #[[Portal:Hidalgo (state)]] – decent appearance; no obvious errors but a narrow scope. 11 excerpts; 16 other images. Routine maintenance. #[[Portal:Sporting Kansas City]] – decent appearance; <strike>one minor display error which looks fixable;</strike><small>(fixed)</small> narrow scope. 11 excerpts; 7 other images. Routine maintenance. #[[Portal:Piciformes]] – decent appearance; no obvious errors but a narrow scope. 9 excerpts; one other image. Routine maintenance. #[[Portal:Birds-of-paradise]] – decent appearance; no obvious errors. 50 excerpts; five other images. Some manual improvements. Currently at [[Wikipedia:Miscellany for deletion/Portal:Woodpeckers|MfD]] with the rationale that woodpeckers are not a family. #[[Portal:Coffee production]] – decent appearance; no obvious errors but a narrow scope. 40+ excerpts; 11 other images. Routine maintenance. #[[Portal:Albanian diaspora]] – decent appearance; no obvious errors but a narrow scope. 30+ excerpts; three other images. Routine maintenance. #[[Portal:University of Nebraska–Lincoln]] – decent appearance; no obvious errors but a narrow scope. 18 excerpts; eight other images. Routine maintenance. Currently at [[Wikipedia:Miscellany for deletion/Portal:University of Arkansas at Pine Bluff|MfD]] with the rationale that [[Portal:University of Arkansas at Pine Bluff]] contains only two articles. #[[Portal:University of Gothenburg]] – decent appearance; no obvious errors but a narrow scope. 10 excerpts; two other images. Routine maintenance. #[[Portal:Transformers]] – decent appearance; no obvious errors but a narrow scope. 40+ excerpts; two other images (everything else is non-free). Some manual improvements. #[[Portal:Boston Celtics]] – decent appearance; no obvious errors but a narrow scope. 40+ excerpts; 16 other images. Routine maintenance. #[[Portal:Newbury Park, California]] – decent appearance; no obvious errors but a narrow scope. 16 excerpts; 34 other images. Routine maintenance. #[[Portal:Vanessa Williams]] – decent appearance; no obvious errors but a narrow scope. 30+ excerpts; two other images. Routine maintenance. #[[Portal:Bette Midler]] – decent appearance; no obvious errors but a narrow scope. 40 excerpts; seven other images. Routine maintenance. #[[Portal:Ozzy Osbourne]] – generally decent appearance <strike>but several minor display errors;</strike><small>(fixed)</small> narrow scope. 50 excerpts; 17 other images. Routine maintenance. #[[Portal:Carnegie Mellon University]] – decent appearance; no obvious errors but a narrow scope. 15 excerpts; 28 other images. Routine maintenance. #[[Portal:Milwaukee]] – decent appearance; no obvious errors. 15 excerpts; 47 other images. Some manual improvements. Too few excerpts but potentially good. #[[Portal:Billings, Montana]] – decent appearance; no obvious errors but a narrow scope. Four excerpts; 27 other images. Some manual improvements. #[[Portal:Empire of Japan]] – decent appearance; no obvious errors but a narrow scope. 40+ excerpts; 20 other images but with a couple of repeats. Routine maintenance. #[[Portal:Cheese]] – decent appearance; no obvious errors. Nine excerpts; 50+ other images. Extensive manual improvements. Too few excerpts but potentially good. {{cob}} :It appears that most of the portals have a narrow scope and should go but a significant minority are either already of a good enough standard to keep or show sufficient potential to merit further attention. This impression is based not on cherry-picking but on a random sample. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 21:42, 21 March 2019 (UTC) ::Thank you for this, this is a very good illustration of why this proposal is too broad - it will delete portals that clearly should not be deleted, and others that may or may not need to be deleted (e.g. I've !voted to merge several of the portals about universities). [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 21:58, 21 March 2019 (UTC) * '''Query''' Why don't we have a CSD for pages created by unauthorized scripts or bots? [[WP:BAG]] exists for a reason right? (And this seems to be a good example of it). [[User: Crazynas|Crazynas]]<sup> [[User_talk:Crazynas|t]]</sup> 21:50, 21 March 2019 (UTC) **{{replyto|Crazynas}} because not all of them should be deleted, as [[user:|Certes]] analysis immediately above demonstrates perfectly. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 21:58, 21 March 2019 (UTC) ::: {{yo|Thryduulf}} You're missing my point. Just like we have a policy that banned users are to be reverted ''in all cases'' not because they might not make good edits (to game the system or not) but because they are a disruption to the community; so we should have a policy that pages created (or edited I suppose) by unauthorized bots are inherently not welcome, ''because'' of the potential for disruption ''regardless of their merit'' (by disruption I'm talking about this AN thread as much as the pages themselves). This is the whole reason we have a group dedicated to overseeing and helping with bots right? [[User: Crazynas|Crazynas]]<sup> [[User_talk:Crazynas|t]]</sup> 22:12, 21 March 2019 (UTC) ::::No bots were involved. The pages were created using a template. One of your last page creations was a user talk page, where you welcomed a new editor using Twinkle. You did a very professional job, by applying a template which introduces the new editor with the sort of carefully considered and neatly arranged prose that we don't have time to write every time a new contributor appears. Using a template is not a valid rationale for mass deletions. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 22:22, 21 March 2019 (UTC) ::::: Curious, what template did you use? I guess the difference I see is the twinkle is highly curated and subject to extensive review (as are the templates it calls). If all these pages were manually created, then what happened in the example of (what to me looks pretty much like G1) that Fram posted above? Why didn't the human that pressed the button take responsibility for that (so to speak) pile of rubbish? To clarify, ''Bot'' here covers scripts, AWB (which is 'manual'), java implementations etc. In short: "Bot policy covers the operation of all bots and automated scripts used to provide automation of Wikipedia edits, whether completely automated, higher speed, or simply assisting human editors in their own work." The policy '''explicitly''' references mass page creation as being under the purview of BAG [[Wikipedia:Bot_policy#Mass_page_creation|here]]. [[User: Crazynas|Crazynas]]<sup> [[User_talk:Crazynas|t]]</sup> 22:39, 21 March 2019 (UTC) ::::::I haven't used any of these templates myself but recent portals have been created by variants on {{tl|Basic portal start page}}. The numbered versions such as {{tl|bpsp6}} cater for portal-specific conditions such as there being no DYKs to feature. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 23:07, 21 March 2019 (UTC) :::: {{replyto|Crazynas}} I was simply answering your question about why we do not speedy delete every page created by an unauthorised bot, etc - simply because not every page created by such means should be deleted. You are also mistaken about banned users - they ''may'' be reverted but they are not required to be. Certes analysis shows that some of the portals created by the script have been improved since, sometimes significantly. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 22:46, 21 March 2019 (UTC) ::::: {{yo|Thryduulf}} Sure, and this is tangential to the proposal here (which I'm still opposing, if you noticed). In any case the thought I'm having wouldn't be applied ex post facto but it ''would'' make it explicitly clear that mass creation of pages by automated or semi-automated means without prior approval is disruptive. [[User: Crazynas|Crazynas]]<sup> [[User_talk:Crazynas|t]]</sup> 23:02, 21 March 2019 (UTC) *'''Comment'''. The problem with many of these recently created template-based portals is that it is difficult or impossible to improve them. I've edited portals for over a decade but cannot work out how to change the portal code to include or exclude a particular article or image. (For articles I believe one has to change the template or mark the article as stub to exclude it; for images I believe it just harvests those from the main topic article.) Thus they are not drafts that could be further improved, they are static uneditable entities for which the only solution is to start from scratch. There is no thought to be preserved that is not equally present in the list of articles in the template/images in the root article. [[User:Espresso Addict|Espresso Addict]] <small>([[User talk:Espresso Addict|talk]])</small> 02:12, 22 March 2019 (UTC) *The key issue is that traditionally, portals are viewed as entry points to broad topic areas. However a page generated by the helper templates that draw content from an underlying navigation box is more akin to a [[second screen]] experience: it provides an X-ray view into the navigation box. It's not clear this is the experience the community wants to provide for readers visiting something labelled a portal. [[User:Isaacl|isaacl]] ([[User talk:Isaacl|talk]]) 20:47, 24 March 2019 (UTC) * the automated scripts are so easy to fool. Even if everything looks perfect when the portal is set up, as soon as someone adds an new link to a nav box (that may make sense in the nav box but not for the portal), adds an image to a page, or creates a DYK completely unrelated to the topic which includes the five letters "horse" within someone's name behind a pipe, you get random inappropriate stuff in an automated portal. The editor adjusting the nav box, adding a picture without a caption per [[WP:CAPTIONOBVIOUS]] or creating the DYK has no idea the portal is being busted. There is no edit to the portal to review so watch listing the portal does not help. You have to manually review the portal display regularly. That is before looking at lua errors. Autogenerated content is a bad idea. Forcing other editors to review your auto generated crap is wrong. Ignoring the guidelines because they are "outdated" and leaving 4500 pages that need to be checked and discussed against the guidelines by other editors is wrong. The only reasonable solution is to nuke these from orbit. Then if someone willing to follow the guidelines and use intellgently designed and applied tools want to recreate some titles, that is fine. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 21:23, 24 March 2019 (UTC) **Everything you say before "The only reasonable solution..." may be true but is irrelevant to this proposal as written. "Nuking them from orbit" is not the only reasonable solution, as fixing the issues so that the portals don't break is also reasonable. As is not deleting the ones that have been fixed so that the errors you talk about don't occur. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 00:46, 25 March 2019 (UTC) **:{{tq2|Portal creation&nbsp;... is down to about a minute per portal. The creation part, which is automated, takes about 10 seconds. The other 50 seconds is taken up by manual activities, such as finding candidate subjects, inspecting generated portals, and selecting the portal creation template to be used according to the resources available. Tools are under development to automate these activities as much as possible, to pare portal creation time down even more. Ten seconds each is the goal.|source=[[Wikipedia:WikiProject Portals/Newsletter archive#Wikipedia:WikiProject Portals update #029, 13 Feb 2019|Portal Update #29, 13 Feb 2019]]}} Someone spent less than 50 seconds creating the page; requiring editors to spend more time than that to delete it has an extortionate effect, even though there's a good faith intent. If we don't nuke from orbit, then those who want these automatically-created portals deleted will be forced to spend far, far more than 50 seconds per portal discussing them one by one (or ten by ten, or one hundred by one hundred, it'll still be a lot of time). 50 seconds "taken up by manual activities" is how we end up with a [[Portal:Sexual fetishism]] that includes [[Pedophilia]] as one of the selected articles–probably not the best selection–but that's been there for five months now. [[User:Levivich|Leviv]]&thinsp;<span style="display:inline-block;transform:rotate(45deg);position:relative;bottom:-.57em;">[[User talk:Levivich|ich]]</span> 03:04, 25 March 2019 (UTC) ***:Two wrongs do not make a right and there is no deadline. The only reason for deleting them all you seem to have is that you don't like that these portals were created so quickly, and that some of them are bad. That's fine, you are entitled to your opinion and some of them are bad. However that does not equate to a reason to delete all of them without checking whether they are good or bad. If you have problems with specific portals then they should be fixed and/or nominated for deletion, as I see you have done in this case, but just because X is bad doesn't mean that the entire set of pages of which is a part should be speedily deleted. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 09:35, 25 March 2019 (UTC) ****:"There is no deadline" is a complete non-argument. There is no deadline to ''have'' these portals either. Knowingly advocating for keeping problematic portals around until someone not only notices it but also decides to MfD it is exposing readers to shitty, thoughtless reproductions of content for no actual benefit (the benefits" of these portals are addressed dequately by the navigation templates they are based on) and with the risk of showing them all kinds of errors which gives a very poor impression. Luckily very few people get actually exposed to these pages, but this also means that the very hypothetical damage deleting some of these pages would do is extremely minimal. [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 10:09, 25 March 2019 (UTC) *****:There was indeed no deadline for their creation, now they have been created that is irrelevant. If we follow your logic though we should delete every article and then just recreate the ones that admins vet as meeting an undefined standard. Yes, deleting more slowly does increase the risk that some readers will see errors, but thtat's exactly what happens in every other namespace without a problem. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 16:35, 27 March 2019 (UTC) ******:No, that's not my logic. Your use of "no deadline" when it suits you, and the dismissal when it doesn't, is quite clear though. Deleting articles is losing content, deleting these auto-portals is losing nothing. Furthermore, we have in the past speedy deleted large groups of articles by one or two creators once it became clear that too many contained errors. This has been done with thousands of articles by Dr. Blofeld, with thousands by Jaguar, and with thousands by Sander v. Ginkel (the last ones moved to draft and then deleted afterwards). Once we know that with one group of creations by one editor, there are many problems, we had no qualms in the past to speedy delete them. That didn't mean that they can't be recreated, or that admins will first vet them, no idea where you get those ideas. Please don't make a caricature of what I support here, and please don't make absolute statements which don't match reality. [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 17:41, 27 March 2019 (UTC) *'''Oppose''' 1) Let's not create precedents where we hand single admins editorial control, admins may well be great editors (some better than others), but let's keep editorial control as much as possible only with all editors. 2) The formulation of this supposed CSD criteria seems to be a [[WP:PUNISH]] against a single user. (As an aside, different perspective: there are perhaps millions of pages in article space that are "poor", so portal space is bound to have them, too - just work through it -- and if we come-up with new forward looking policies and guidelines for all portals (or mass creations) consistent as possible with the 5 P, all the better). [[User:Alanscottwalker|Alanscottwalker]] ([[User talk:Alanscottwalker|talk]]) 17:42, 25 March 2019 (UTC) *'''Support''' - per Fish & Karate. [[User:Ealdgyth|Ealdgyth]] - [[User talk:Ealdgyth|Talk]] 16:08, 27 March 2019 (UTC) *'''Oppose'''. I feel there are much better ways of handling the situation, including but not limited to: expanding P2, Portal PROD, and even MFD. This is too broad of a sword that doesn't even cut in the right places since it's only limited to one user in a given time frame. --[[User:Tavix| <span style="color:#000080; font-family:georgia">'''T'''avix</span>]] <sup>([[User talk:Tavix|<span style="color:#000080; font-family:georgia">talk</span>]])</sup> 16:15, 27 March 2019 (UTC) *Thought I had voted here but I guess I hadn’t. Regardless, my thinking on this has changed because of Certes’ in-depth analysis of TTH’s portal creations. Anyway: '''Oppose'''. The mass creation of portals is something that should be dealt with preferably quickly, but this proposal as written is not the right way to do it. Sure, there are a lot of crappy portals that could be deleted fairly uncontroversially, but there are also a lot of good portals as well as edge cases that deserve more community discussion on whether they should be deleted, or at least a longer waiting period so users may object. —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua"> python</span><span style="color:#ff0">coder </span></span>]] ([[User talk:pythoncoder|talk]] &#124; [[Special:Contribs/pythoncoder|contribs]]) 12:40, 29 March 2019 (UTC) *<s>'''Oppose''' for now. I still hope that the proposal might become limited to portals looked at and determined to be poor by some objective criteria, which I could support, but that hasn't yet happened. Speedy ''ad hominem'' deletion regardless of subsequent tuning, current quality or even potential for future improvement is likely to throw too many babies out with the bathwater. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 12:46, 29 March 2019 (UTC)</s> <small>Duplicate !vote stricken. [[User:GoldenRing|GoldenRing]] ([[User talk:GoldenRing|talk]]) 10:14, 4 April 2019 (UTC)</small> *'''Regretfully support''': as an editor I dislike the idea of creations made by certain users being deleted ''en masse'' but, quite frankly, MfD cannot cope with the influx at the moment. Hell, I've got a decent laptop and MfD is getting so big scrolling down causes a bit of lag. '''<span style="font-family: Arial">[[User:StraussInTheHouse|<span style="color: red">SITH</span>]] [[User talk:StraussInTheHouse|<span style="color: blue">(talk)</span>]]</span>''' 20:57, 30 March 2019 (UTC) *'''Strong support''' of something to this effect, per [[WP:MASSCREATION]] and [[WP:TNT]] (i.e., the babies thrown out with the bathwater can be recovered later). However, opponents raise good points of localizing control to a [[WP:TINC|few members]], and while I do argue that '''portals are not content, they are a navigational tool''', so community control of them can be a bit "stricter" than mainspace articles, perhaps something like PROD would be better. Regardless of how this pans out, for future portals going forward I proposed Portals for Creation at RfC, and created a mockup [[User:John M Wolfson/Portals for Creation|here]] if anyone wants a look. <!-- Template:Unsigned --><small class="autosigned">—&nbsp;Preceding [[Wikipedia:Signatures|unsigned]] comment added by [[User:John M Wolfson|John M Wolfson]] ([[User talk:John M Wolfson#top|talk]] • [[Special:Contributions/John M Wolfson|contribs]]) </small> *:Why is requiring administrators to comb through deleted portals to find those that should not have been deleted in order to restore them, having inconvenienced those people who use the portals in the mean time, in any way better for the project than deleting only those that need to be deleted? [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 07:34, 4 April 2019 (UTC) *'''Support'''. Worthless pages which take 12 seconds to create shouldn't take more than 50000 times that for multiple users to delete. If a subject WikiProject or person interested in the portal's subject is willing to "adopt" that portal, or even assert that the portal is not useless, a more nuanced consideration may apply. And, I should point out, some of the individual deletions are incomplete, as user-facing pages (mostly categories and navigation templates, but some actual article pages) still point to the deleted portals. — [[User:Arthur Rubin|Arthur Rubin]] [[User talk:Arthur Rubin|(talk)]] 10:25, 4 April 2019 (UTC) {{abot}} ===Proposal 5: Shut down or reform [[WP:WikiProject Portals|WikiProject Portals]]=== I know, me proposing shutting down a WikiProject I'm in? What am I thinking? Well, I mainly joined to make sure things would go smoothly after that RfC to delete all portals - clearly it has not. As thus, I think a solution (among the others) would be to '''shut down the WikiProject responsible for many of the bad portal creations'''. Right now it appears all its doing is creating new portals, not maintaining or improving them - which is what a WikiProject is supposed to do. However, a less extreme solution would be to '''reform the project''' to actually maintain and improve the portals it creates, and creates portals sparingly. I'm fairly certain a task force making sure portals meet standards would be beneficial to the issue, and also making it clear that not everything needs a portal. I'm going for the latter option to reform - however, I'm going to leave the shutdown option up in the air in case people find good reason for it to be considered. '''Addendum 13:48, 15 March 2019 (UTC)''' - Since I forgot to clarify ({{selftrout}}) here's two examples of reforms I could see being useful: *A quality scale for portals, like we use for articles - this could help with knowing which portals are good and which ones need improvement *Dividing the Project into task forces to make sure necessary tasks for the maintenance of portals are completed, as right now they clearly are not :*Sub-reform for this would be to make a task force that deletes bad portals that don't meet quality standards and are not needed Hopefully this can help clarify this proposal somewhat - if none of these can be done reasonably (which I doubt they can't) the shutdown option should be considered. <span style="color:#FFB3FF;">Kirbanzo</span> <sup>([[User:Kirbanzo|userpage]] - [[User talk:Kirbanzo|talk]] - [[Special:Contributions/Kirbanzo|contribs]])</sup> 23:01, 14 March 2019 (UTC) ====Survey on sub-proposal to shut down WikiProject Portals==== {{atop | status = | result = There is a strong [[WP:SNOW]] consensus against shutting down WikiProject Portals. (involved close) —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua"> python</span><span style="color:#ff0">coder </span></span>]] ([[User talk:pythoncoder|talk]] &#124; [[Special:Contribs/pythoncoder|contribs]]) 15:39, 20 March 2019 (UTC) {{nac}} }} *'''Neutral''' as per above. <span style="color:#FFB3FF;">Kirbanzo</span> <sup>([[User:Kirbanzo|userpage]] - [[User talk:Kirbanzo|talk]] - [[Special:Contributions/Kirbanzo|contribs]])</sup> 23:01, 14 March 2019 (UTC) *'''Strong oppose''' firstly this is the wrong forum, secondly there is nothing in the nomination that explains why this is needed, or how it will result in an improvement to the encyclopaedia. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 01:04, 15 March 2019 (UTC) * '''Oppose'''. [[User:Abyssal|Abyssal]] ([[User talk:Abyssal|talk]]) 01:35, 15 March 2019 (UTC) *'''Support''': might as well. --[[User:K.e.coffman|K.e.coffman]] ([[User talk:K.e.coffman|talk]]) 02:23, 15 March 2019 (UTC) *'''Oppose'''. There is consensus to keep the portal system but it has many faults, so a focus for improving it seems sensible. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 14:36, 15 March 2019 (UTC) *'''Strong oppose'''. Not necessary and not the best way to fix Wikipedia’s portals. —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua"> python</span><span style="color:#ff0">coder </span></span>]] ([[User talk:pythoncoder|talk]] &#124; [[Special:Contribs/pythoncoder|contribs]]) 19:49, 15 March 2019 (UTC) *'''Oppose''' – Would amount to [[Don't throw the baby out with the bathwater|throwing the baby out with the bathwater]]. <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 01:19, 16 March 2019 (UTC) *'''Oppose''' For the same reasons. — AfroThundr <sup>([[User:AfroThundr3007730|u]] · [[User talk:AfroThundr3007730|t]] · [[Special:Contributions/AfroThundr3007730|c]])</sup> 19:48, 16 March 2019 (UTC) *'''Oppose''' For the same reasons. [[User:Bermicourt|Bermicourt]] ([[User talk:Bermicourt|talk]]) 22:37, 16 March 2019 (UTC) *'''Support''' Incompetent project that doesn't want to deal with the crud their members create. [[User:CoolSkittle|<span style="background-color: blue; color: orange">CoolSkittle</span>]] ([[User talk:CoolSkittle|talk]]) 18:07, 17 March 2019 (UTC) ** Invalid rationale. See [[Wikipedia:Arguments to avoid in deletion discussions#Surmountable problems]]. <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 20:58, 17 March 2019 (UTC) *'''Oppose''' as {{em|ad hominem}} vindictiveness. The only rationale for deleting such a project would be a proper community-wide decision to eliminate all portals. This is not the venue for that; [[WP:VPPOL]] is. And this is not the venue for deletion of a wikiproject; [[WP:MFD]] is. [[WP:Process is important]], most especially in deletion discussions and related matters, because damned near zero people are going to look for such discussions in an admins' "house organ" page like this. Hardly any non-admins watchlist this page or pay any attention at all to what is said here. It is not intended to be a venue for community-wide concerns in the first place, and even with belated addition to [[WP:CENT]], discussing such matters here is a special kind of forum shopping, namely an attempt to appeal to a small cadre of specialist editors whose concerns about maintenance (and cop-like role of "going after" people for alleged behavioral flaws, often with little oversight, especially compared to [[WP:ANI]] process) will colour everything they do and say about the matter. <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 20:58, 17 March 2019 (UTC) ::Widely misleading arguments. This is a widely advertised and widely participated discussion. It came from a VPR discussion, linked from the very beginning. There are far more non-Admins than Admins involved here. Try to stick to facts. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 23:23, 17 March 2019 (UTC) * '''Support''' [non-admin comment :] opposed to portals, they harvest legitimate contributions yet the creators expect them to be automatically protected as legitimate contributions and outside of normal guidance on creation. There are cadres of users who think this is what wikipedia is about, or at least it is a way of making a big splash without knowing anything but how to tweak code (and then wikilawyer when challenged). [[User talk:Cygnis insignis|cygnis insignis]] 06:10, 18 March 2019 (UTC) *'''Oppose''' Punishing a whole community for the actions of one person is not reasonable. [[User:Waggers|<b style="color:#98F">W</b><b style="color:#97E">a</b><b style="color:#86D">g</b><b style="color:#75C">ge</b><b style="color:#83C">r</b><b style="color:#728">s</b>]][[User talk:Waggers|<small style="color:#080">''TALK''</small>]] 16:49, 18 March 2019 (UTC) *'''Oppose''' per above. [[WP:SNOW|It's getting cold out...]] [[User:SemiHypercube|<b style="color:#090">Semi</b>]][[User talk:SemiHypercube|<i style="color:#099">Hyper</i>]][[Special:Contributions/SemiHypercube|<u style="color:#009">cube</u>]] 16:52, 18 March 2019 (UTC) *'''Oppose''' In the grand scheme of things I'd like to see portals deprecated, but doing so is not where the community is at right now. If there is consensus to keep portals, having a wikiproject to maintain them seems like a good idea. <small>I also feel [[WP:SNOW|cold]]...</small> [[User:Wugapodes|Wugapodes]] [[User talk:Wugapodes|[t<sup>h</sup>ɑk]]] [[Special:Contributions/Wugapodes|[ˈkan.ˌʧɹɪbz]]] 06:47, 19 March 2019 (UTC) {{abot}} ====Survey on sub-proposal to reform WikiProject Portals==== *'''Support''' as proposer and per above. <span style="color:#FFB3FF;">Kirbanzo</span> <sup>([[User:Kirbanzo|userpage]] - [[User talk:Kirbanzo|talk]] - [[Special:Contributions/Kirbanzo|contribs]])</sup> 23:01, 14 March 2019 (UTC) *'''Oppose''' as the proposal is in the wrong forum and contains no details of what reform is being suggested, let alone how these reforms would solve the issues identified. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 01:05, 15 March 2019 (UTC) :*This is related to the discussion as the WikiProject is headed by the user being discussed here. <span style="color:#FFB3FF;">Kirbanzo</span> <sup>([[User:Kirbanzo|userpage]] - [[User talk:Kirbanzo|talk]] - [[Special:Contributions/Kirbanzo|contribs]])</sup> 13:48, 15 March 2019 (UTC) :**Wikprojects are a collection of editors, not just one person. There is no evidence presented that there is any admin action required regarding the WikiProject as a whole collectively (not that I can immediately think of what that action could look like if it were), and there isn't even consensus that admin action regarding the single editor is required. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 13:57, 15 March 2019 (UTC) ::*Fair point, but considering the discussion below it should still be considered. <span style="color:#FFB3FF;">Kirbanzo</span> <sup>([[User:Kirbanzo|userpage]] - [[User talk:Kirbanzo|talk]] - [[Special:Contributions/Kirbanzo|contribs]])</sup> 14:00, 15 March 2019 (UTC) *'''Support'''. I’ve been doing some reform work of this type by creating a [[WP:WikiProject Portals/Cleanup|page to clean up some of the damage done to the older portals.]] WikiProject Portals has an [[WP:WPPORT/A|assessment page]] but I’m not sure how much it gets used. —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua"> python</span><span style="color:#ff0">coder </span></span>]] ([[User talk:pythoncoder|talk]] &#124; [[Special:Contribs/pythoncoder|contribs]]) 19:49, 15 March 2019 (UTC) ** It took quite a lot of discussion to form a consensus for those assessment criteria. Any portals would need to be evaluated against them to ensure they meet at least minimal quality standards (not including the other criteria in the portal guidelines). It will take a while to go through all of the portals and rate them on the quality scale, and that is one of our backlog tasks. — AfroThundr <sup>([[User:AfroThundr3007730|u]] · [[User talk:AfroThundr3007730|t]] · [[Special:Contributions/AfroThundr3007730|c]])</sup> 19:48, 16 March 2019 (UTC) *'''Support''' in theory. It makes more sense than the above "I disagree with you so I will try to just erase you" bullshit. However, it's not at all clear that the wikiproject, as such, needs any "reform"; rather, some specific decisions and actions taken by its participants have turned out to be controversial, and the community will discuss that (hopefully in a more sensible venue like [[WP:VPPOL]]), and the wikiprojects should abide by the result of that process. We don't have any indication this would not happen, so there isn't actually a "reform" to perform, nor is there yet any consensus of what form that should take anyway. Some people here seem to be under the impression that WP is going to come out against portals; others that it'll be against automated portals; others that it'll be against portals on minor topics (and sub-sub-sub-topics) that people aren't likely to seek a portal for; others that nothing is actual broken; others that .... There isn't a single direction of "reform" being proposed. <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 21:03, 17 March 2019 (UTC) *'''Oppose''' The way to reform a project is to get involved with it. We've already had multiple discussions about how the project should be structured and how it should operate on the project pages themselves, and further suggestions there are always welcome. But proposing "reform" without specifying what particular changes are being suggested isn't exactly helpful. [[User:Waggers|<b style="color:#98F">W</b><b style="color:#97E">a</b><b style="color:#86D">g</b><b style="color:#75C">ge</b><b style="color:#83C">r</b><b style="color:#728">s</b>]][[User talk:Waggers|<small style="color:#080">''TALK''</small>]] 16:53, 18 March 2019 (UTC) ::Exactly right, {{u|Waggers}}, exactly right. You've hit the nail on the head. [[User:Swarm|<span style="color:Green">'''~Swarm~'''</span>]] [[User talk:Swarm|<span style="color:DarkViolet">'''{talk}'''</span>]] 18:22, 20 March 2019 (UTC) :::But is you see little need for portals why get involved? [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 23:18, 21 March 2019 (UTC) ::::Nominating hundreds of portals for deletion ''is'' getting involved. If you see little need for them then fine, live and let live, they're not doing you any harm. The community has decided to keep portals, so either you respect that consensus and ignore them, or you respect that consensus and get involved with resolving whatever problem you have with them. [[User:Waggers|<b style="color:#98F">W</b><b style="color:#97E">a</b><b style="color:#86D">g</b><b style="color:#75C">ge</b><b style="color:#83C">r</b><b style="color:#728">s</b>]][[User talk:Waggers|<small style="color:#080">''TALK''</small>]] 12:58, 3 April 2019 (UTC) *'''Qualified support'''. The current project is far from perfect but it's hard to give unqualified support without a statement of specific reforms. We don't want thousands more portals, but last year's RfC shows that it would be equally inappropriate to "reform" into WikiProject Nuke All Portals From Orbit. I removed my name from the project's roster when portal creation grew rapidly. Since then I have done some maintenance but I see little point in improving pages that other editors are working so hard to delete. I could rejoin a project that combined improved existing portals with the right blend of identifying poor, narrow portals for deletion and creating portals in small numbers where clear gaps exist. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 13:32, 29 March 2019 (UTC) ====Discussion on proposal to reform WikiProject Portals==== *'''Query''' {{ping|Kirbanzo}} - do you have any early thoughts about what some good reforms would be to shift the primary focus of the project towards maintenance/improvement over creation? [[User:Nosebagbear|Nosebagbear]] ([[User talk:Nosebagbear|talk]]) 23:11, 14 March 2019 (UTC) :*See addendum. <span style="color:#FFB3FF;">Kirbanzo</span> <sup>([[User:Kirbanzo|userpage]] - [[User talk:Kirbanzo|talk]] - [[Special:Contributions/Kirbanzo|contribs]])</sup> 13:50, 15 March 2019 (UTC) * Transcluded to [[Wikipedia talk:WikiProject Portals]]. [[User:Pppery|&#123;&#123;3x&#124;p&#125;&#125;ery]] ([[User talk:Pppery|talk]]) 23:36, 14 March 2019 (UTC) * No, do not transclude important discussions from AN to the relevant talkpage. Hold the discussion on the relevant talk page. Transclude to here is there is good reason, which there is not. Holding hte discussion here means watchlisting it doesn't work, and it wont be archived in the right place. Shutting down a WikiProject is not in scope for WP:AN. --[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 02:20, 15 March 2019 (UTC) * The Portals Wikiproject members can't even come up with a proper new guideline for what topics get a portal even when faced with a village pump imposed moratorium. The discussion is all over the place with no focus. Heck they did not even follow their old guideline about picking subjects broud enough to gain reader and editor interest. The only thing they appear to agree on is MORE MORE MORE and using [[WP:VITAL]] as a to do list. Their newsletter said they are pushing to 10,000 portals (off a base of 1500 old line portals). Now the number of portals will shrink until and unless they get new guidelines passed by an RFC. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 09:57, 15 March 2019 (UTC) * That old guideline wasn't generally followed, ever. That's because portals (except those on the main page) get about 1 to 3 percent of the amount of traffic that their corresponding root articles get. In other words, "not a lot". That's because almost all their traffic comes via WP internal links. Almost nobody googles "Portal". So, for the vast majority of topics, large numbers of readers and editors will never be forthcoming, and never were. Out of the 1500 portals, about 100 had maintainers (maintained by around 60 editors), and maybe 20% of them regularly edited the portals they maintained. : The WikiProject, and the community, need feedback in the form of hard numbers, in order to get a sense of what will even get used. How hard would it be to make a chart listing all the portals in one column, and their page views for the past month in the second column, and then sort the chart by the second column? That might provide some insight. <span class="nowrap">&nbsp;&nbsp; &mdash; ''[[User talk:The Transhumanist|The&nbsp;Transhumanist]]''&nbsp;&nbsp; </span> 11:05, 15 March 2019 (UTC) *''Sigh''. :TTH, you had this data already. You know that portal pageviews are miniscule. At the RFC on deleting the portal namespace, stats were posted on pageviews, and not even all the portals linked from the front page had decent viewing rates. :Yet despite knowing all that, you personally created thousands of new portals, despite having all the evidence in front of you that they are useless. :And when I presented the evidence to you again, and asked you to desist, you were furious. Instead of assessing the issues, you posted multi-screenfull unfocused ramblings replete with shouts of "bias", "personal attack" etc. :The problem is not any shortage of information. The problem is that as @[[User:Legacypac|Legacypac]] notes above, the discussions in the WikiProject have no focus, no regard for available evidence, and no respect for community consensus. :Legacypac and usually disagree, but in this case we see exactly the same problem: a WikiProject which has a long and sustained track record of being utterly incapable of acting responsibly wrt the page within its purview. :This is not ''solely'' TTH's doing. TTH bears by far the highest responsibility because TTH has been both the most prolific creator and the most angry objector to calls for restraint, but several other regulars at WikiProject Portals have been equally unfocused and equally bonkers. For example: :* Only 15 days ago, [[WT:Portal/Guidelines#Portal_creation_and_deletion_criteria_proposal_5]]: a proposal to create portals up to VA Lelevl 5, when even VA level 4 would be about 10,000 portals. :* 17 days ago, [[WT:Portal/Guidelines#Portal_creation_and_deletion_criteria_proposal_1]]: a proposal for portals to have minimum of only 20 articles. Precisely the sort of microportal of which the community has had enough. :So the community simply cannot rely on this group to set and uphold resposnsible guidelines. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 11:53, 15 March 2019 (UTC) :: {{ping|BrownHairedGirl}} I make the proposal 5. And it was a proposal. I '''Support''' a reform in WikiProject Portals. My idea is the existence of approximately 1000(level 3) single page portals layout, directly linked in tree model with the main page. The role of the wikiproject should be to organize this tree and develop tools to transform all portals into single-page layout portals.[[User:Guilherme Burn|Guilherme Burn]] ([[User talk:Guilherme Burn|talk]]) 12:11, 15 March 2019 (UTC) :::@[[User:Guilherme Burn|Guilherme Burn]], no technical diversions. My point is not about ''how'' the portals operate; it's about their scope. And 20 pages is insanely narrow. A 20-page portal is just an bloated navbox. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 12:36, 15 March 2019 (UTC) ::{{ping|BrownHairedGirl}} I'm trying to figure out how you've come to the conclusion that WPPORT completely ignores evidence and consensus. The project discussions I've participated in have been rational and reasonable, and far from unfocused. Also, please try not to conflate individual editors' behavior with the project as a whole. I've seen no evidence that the WikiProject has acted irresponsibly regarding the Portal system. If you're referring to the several thousand new portals created by TTH, you should keep in mind that WikiProjects don't have any actual authority to dictate who can and can't create something (even if we were opposed to creating new portals). That's what guidelines are for. ::We've been working to develop updated criteria for the [[WP:PORTG|Portal guidelines]] since November (rebooted from even earlier discussions in April) - which you already know, since you've participated as well. We're still working on the guidelines so that we have better, more concrete criteria to judge new and existing portals against (and which would make MfD easier for those that fail). Once we've developed consensus on these, they can be applied to the namespace to fix the portals that can be fixed, and remove the ones that can't (new or old). <small>(Side note: Anyone with input or ideas is welcome to participate at [[WT:PORTG]].)</small> ::Actions in the Portal namespace itself (for most of us, it seems) has mostly been technical fixes and tweaks to our tools. Also, your not agreeing with particular proposals does not make those proposing them irresponsible or incompetent. Talk pages are a place to discuss new ideas so that we can find the benefits and drawbacks of each. If we constantly had to worry about being labeled as irresponsible or incompetent for suggesting something, we'd never have any new ideas or get anything done. I've made plenty of suggestions that didn't pan out later, as I'm sure you have, and everyone else here. That's how we learn what works and what doesn't and build a better encyclopedia. In the end, that's what we're all here for right? — AfroThundr <sup>([[User:AfroThundr3007730|u]] · [[User talk:AfroThundr3007730|t]] · [[Special:Contributions/AfroThundr3007730|c]])</sup> 19:48, 16 March 2019 (UTC) :::{{yo|AfroThundr3007730}} that's not at all how it looks from outside. :::# Last year, the project began developing automated portals, whose advocates claimed need little or no curation. No attempt was made to hold an RFC to determine whether the community found these automated portals to be a worthwhile addition. (I think I see an emerging consensus that they are not useful, or maybe useful only in some curcumstances) :::# Following the [[WP:ENDPORTALS]] RFC which decided not to actually delete the whole portal namespace, the project decided to massively expand the number of portals, despite the clear evidence at RFC that many editors wanted ''fewer'' portals. At no point did the project initiate an RFC to establish whether there was a community consensus for the project's enthusiasm to bizarrely interpret "don't TNT the lot" as "create thousands more". :::# You are right that a WikiProject has no powers of restraint on an individual editor. However, the project does have an ability to watch what is done, and to act a venue to monitor inappropriate creations, and to initiate cleanup as needed. I see no sign at all that the project has done ''any'' of that ... and on the contrary, when outsiders have challenged TTH's sprees of portalspam, other project members have rallied to TTH's defence. :::# Even now, as a cleanup is underway, I see next to no assistance from project members. V few even comment in the MFDs. For example, take the most extreme case so far: [[WP:Miscellany for deletion/Portal:University of Fort Hare|MFD Portal:University of Fort Hare]], an utterly absurd creation for which there exists precisely zero relevant selected articles ... yet ''none'' of the project regulars is visible.<br/>In my view, a WikiProject which shows zero interest in removing inappropriate pages within its scope is dysfunctionally irresponsible. :::# The project's efforts to develop guidelines have been exceptionally poor. The discussions have been rambling and unfocused, with a persistent failure to distinguish between factors such as technical ability to create, availability of editors to maintain and monitor, actual usage data, etc. :::# Above all, none of the proposals has been put to an RFC to gauge community consensus, so the guideline discussion have effectively been the work of a small group of editors who are united by a common desire to massively increase the number of automated portals. :::# The result of this failure has been a walled garden of thousands of micro-portals, sustained only by the enthusiasm of the portal project ... and the absolutely inevitable massive shitstorm at the village pump. ::::What this needs now is a structured RFC, which brings together some or all of the proposals made at the project, adds proposals from outside the project, and seeks a community consensus. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 20:18, 16 March 2019 (UTC) ===Proposal 6: Proposed Deletion for portals=== {{atop|Withdrawn by proposer. —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua"> python</span><span style="color:#ff0">coder </span></span>]] ([[User talk:pythoncoder|talk]] &#124; [[Special:Contribs/pythoncoder|contribs]]) 17:13, 29 March 2019 (UTC)}} '''Proposal:''' Create a [[WP:PROD|proposed deletion]] criterion for portals created on April 8, 2018 or later by any user. Per normal PROD rules, the page would be deleted after 7 days, but a user who objects to the deletion may remove the prod template. However, unlike regular PROD, the creator would not be allowed to remove the template (though they would of course be allowed to contest it on the talk page). —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua"> python</span><span style="color:#ff0">coder </span></span>]] ([[User talk:pythoncoder|talk]] &#124; [[Special:Contribs/pythoncoder|contribs]]) 21:58, 22 March 2019 (UTC) *'''Support''' as proposer. I proposed this to resolve issues raised by various opposers. This would provide a longer waiting period before deletion, reduce the chances that the recently created portals that comply with the [[WP:POG|portal guidelines]], and not restrict it to a single user, because there were other users who created problematic portals. Possible reasons for removing a prod template include the portal meeting the portal guidelines or being under active development. —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua"> python</span><span style="color:#ff0">coder </span></span>]] ([[User talk:pythoncoder|talk]] &#124; [[Special:Contribs/pythoncoder|contribs]]) 21:58, 22 March 2019 (UTC) *'''Comment'''. I'm leaning support, having been mulling over proposing something like this myself but, I'm not certain this proposal is quite right yet. I don't think there should be a list of acceptable reasons to deprod, rather a non-exhaustive list of examples to reduce the chance of wikilawyering about it (and there will be situations we don't think of and probably some we do that we shouldn't list per [[WP:BEANS]]). Any restriction on creators deprodding needs to come with exceptions for reverting obvious vandalism and where prod is not permitted (e.g. doesn't meet the criteria, previously kept in a discussion, etc) - it may be better to say creators ''should not'' rather than ''must not''. I also think it important that prodded portals show up in article alerts before this goes live (I no idea if this would require any changes to bot code or not, and if it does how significant it might be). [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 22:21, 22 March 2019 (UTC) **I've now [[Wikipedia talk:Article alerts/Feature requests#Portal prods|asked]] the article alert bot maintainer those questions. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 22:32, 22 March 2019 (UTC) *::The deprod "criteria" are suggestions and not part of the proposal. —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua"> python</span><span style="color:#ff0">coder </span></span>]] ([[User talk:pythoncoder|talk]] &#124; [[Special:Contribs/pythoncoder|contribs]]) 01:56, 23 March 2019 (UTC) *:::I wasn't certain either way, so thanks for clarifying. I do think though that jumping straight in to an RfC without workshopping the proposal first was a poor choice though - there is a good idea but it needs refining before I am comfortable supporting it. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 03:11, 23 March 2019 (UTC) *Prod isn't going to do anything except delay MFD for a week so long as there's multiple users who think all portals, however narrow, should be kept. [https://en.wikipedia.org/w/index.php?title=Special:Log/delete/Cryptic&offset=20190205032152&limit=112&type=delete&user=Cryptic And there are]. —[[User:Cryptic|Cryptic]] 23:35, 22 March 2019 (UTC) **Do multiple users think that? I certainly don't; I just oppose the view that all should be deleted. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 23:43, 22 March 2019 (UTC) **{{replyto|Cryptic}} How does a link to a deletion log support the assertion that there are multiple users who think all portals should be kept? I'm one of the most (perhaps even the most) vocal advocates against the proposed speedy deletion criterion, yet I do not hold that view. I've repeatedly explained that I simply think that only some of the portals should be deleted, and that it is more important to get it right than to do it quickly - [[WP:DEADLINE|there is no deadline]]. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 00:09, 23 March 2019 (UTC) * '''Oppose'''. This would be a pseudo-CSD failing [[WP:NEWCSD]]. Better to list or reference all new templated portals in a big MfD. —[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 00:18, 23 March 2019 (UTC) *'''Comment''' some users think all portals, no matter how narrow or inappropriate the topic, need to be debated at MfD. SmokeyJoe wants a 3500 portal MfD yet NorthAmerica1000 is complaining about a 6 fruit portals being bundled. A lot of unreasonable positions here. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 01:00, 23 March 2019 (UTC) *'''Comment''' What's to stop the group behind the auto-portals removing every PROD? [[User:CoolSkittle|<span style="background-color: blue; color: orange">CoolSkittle</span>]] ([[User talk:CoolSkittle|talk]]) 01:24, 23 March 2019 (UTC) **I would imagine exactly the same thing that stops (groups of) editors systematically removing prods from any given set of articles - doing so is disruptive editing - just as systematically tagging any large set of articles without considering them is (see also [[WP:FAITACCOMPLI]] and [[WP:SK]] points 2 and 3). [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 03:16, 23 March 2019 (UTC) *'''Comment''' check out some of the comments here [[Wikipedia:Miscellany for deletion/Portal:Alhambra, California]] where all portals prior to the reboot survived a deletion discussion as acceptable and any similar ones are therefore acceptable. No one followed the guidelines because they don't matter anymore. Amazing stuff. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 01:57, 23 March 2019 (UTC) * Transcluded to [[Wikipedia talk:Proposed deletion]]. [[User:Pppery|&#123;&#123;3x&#124;p&#125;&#125;ery]] ([[User talk:Pppery|talk]]) 02:34, 23 March 2019 (UTC) *{{sbb}}'''Oppose''' CSD is better, this just sounds like MfD with extra steps. [[User:SemiHypercube|<b style="color:#090">Semi</b>]][[User talk:SemiHypercube|<i style="color:#099">Hyper</i>]][[Special:Contributions/SemiHypercube|<u style="color:#009">cube</u>]] 11:19, 28 March 2019 (UTC) *'''Oppose''' I can think of at least three editors who would make it their duty in life to automatically remove a PROD with the rationale, "Controversial; take to MfD". Which makes this a waste of everyone's time. [[User:Serial Number 54129|<span style="color:black">'''——'''</span>]][[Special:Contributions/Serial Number 54129|<span style="color:black">''SerialNumber''</span>]][[User talk:Serial Number 54129|<span style="color:#8B0000">54129</span>]] 11:25, 28 March 2019 (UTC) *'''Oppose''' Being created recently is not a rationale for deletion, let alone semi-speedy deletion. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 13:35, 29 March 2019 (UTC) {{abot}} ===Proposal 7: Toss it to the WikiProjects=== ''<small>I suggested at the Arbcom case that this be imposed by motion as an interim measure, but I'll put it as a proposal here to allow people to support or oppose it.</small>''<br>Proposal: All editors intending to create a portal must consult with the relevant WikiProject for that topic as to whether they feel a portal would be useful. All existing portals should be raised at the talk pages of the relevant WikiProjects and deleted if there is no consensus at any one of those projects that the portal should be kept. If the topic has no relevant WikiProject, it should be deleted.&nbsp;&#8209;&nbsp;[[User:Iridescent|Iridescent]] 10:42, 29 March 2019 (UTC) *'''Support''' as proposer. This would have the advantages of avoiding bulk speedy deletions, avoiding personalising disputes or naming-and-shaming individuals on either the pro or anti side, avoiding flooding MFD, putting the decision on each portal in the hands of those who actually know about that topic and can make an informed call as to whether the portal would be potentially useful (if a topic is so obscure that it doesn't have a relevant project, then it's reasonable to assume that it's unlikely there are sufficient people with an interest in the topic to maintain or use a portal), and providing an opportunity to neutrally assess whether the older portals are still deemed to be serving a useful purpose. The process could probably be largely automated; a bot could presumably scrape the WikiProjects listed on the talk page of the parent article for each portal, and post a "Do you find this portal useful?" question to the talk pages of those projects, and after a reasonable time (presumably 30 days) we could then go through at leisure and see which portals are considered worth keeping. It might annoy some projects, as e.g. [[WT:WikiProject Food]] or [[WT:WikiProject United States]] will be flooded with 50 different discussions, but unless we're going to speedy delete or speedy keep every portal there will be a flooding effect somewhere, and at least this way it spreads the flood to a manageable level across multiple pages, rather than dumping 4000 pages into [[WP:MFD]] or [[CAT:EX]].&nbsp;&#8209;&nbsp;[[User:Iridescent|Iridescent]] 10:42, 29 March 2019 (UTC) *'''Support''' as the compromise candidate. It's not guaranteed to annoy no-one or be loved by all, but it's better than, as we seem to be enjoying atm, a process that annoys more and is loved even less... [[User:Serial Number 54129|<span style="color:black">'''——'''</span>]][[Special:Contributions/Serial Number 54129|<span style="color:black">''SerialNumber''</span>]][[User talk:Serial Number 54129|<span style="color:#8B0000">54129</span>]] 10:56, 29 March 2019 (UTC) **It won't be liked by anyone, as it concentrates the decision-making in the hands of small cliques of people, but at least it (1)&nbsp;spreads the load regarding where the discussions take place, (2)&nbsp;notifies people interested in the topics who may not be aware of the existence of the portals, and (3)&nbsp;means the fate of [[Portal:London transport]] is decided by people who have an interest in either London or Transport and hopefully have a better idea than the rest of us of what would be useful to readers.&nbsp;‑&nbsp;[[User:Iridescent|Iridescent]] 11:05, 29 March 2019 (UTC) *{{ec}} '''Support with minor tweaks''': To avoid flooding WikiProjects there should be a limit on the number of concurrent discussions on each project (somewhere in the 5-10 region would be my first suggestion) and the 30-day deadline should not be absolute - e.g. if discussion is ongoing at that point there is no rush to close it, equally if consensus is abundantly apparent (by the standards of [[WP:SNOW]]) before that there is no reason to delay taking any necessary action or inaction. Discussions should also be framed neutrally (i.e. don't describe it as "spam", "worthless", "essential" or anything like that.) Also, to avoid edit warring, arguments, etc there should be no extended discussion of which projects are asked - if any editor in good faith believes that a project is worth asking then they are worth asking. Finally there should be a list kept somewhere (probably at the portals project) of which projects have been asked about which portals so the same project doesn't get asked about repeatedly. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 11:15, 29 March 2019 (UTC) *:On the neutral point, it might be worth agreeing a standard wording that can be added with a template that also provides links to basic information about portals so people don't have to keep repeating themselves. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 11:18, 29 March 2019 (UTC) *'''Strong Oppose''' - See the bullet points below for my various rationales. :*Wikiprojects are perenially understaffed and underwatched, with some having no participation for months or even years at a time on their talk pages. Some are marked as semi-active or inactive. Making it a requirement to consult with projects with such problems would amount to muzzling portal creations for many topics, because nobody may actually come along to discuss a portal proposal. :*This proposal would further denigrate Wikipedia in the wrong direction, with an increasing [[nanny state]] type of governance regarding content, where permissions have to first be made to create pages. This would result in even more chilling effects than already exist in various areas of the encyclopedia at this time. :*The proposal goes entirely against the grain of [[WP:5]], point #5, concerning being [[WP:BOLD]]. Wikipedia having no firm rules is one of the fundamental principles of the encyclopedia. The proposal also goes against the grain of [[WP:NOTBUREAUCRACY]] in several ways. :* Regarding the notion that if a topic has no project, the portal would then be procedurally deleted: some topics may not have a direct Wikiproject, but may have a related one. For example, there is no direct project for the topic of air conditioning, but a related project would be WikiProject Engineering. ::Furthermore, many of the discussions listed at [[Wikipedia:WikiProject Council/Proposals|WikiProject Council/Proposals]] receive very little input, sitting in limbo. If a Wikiproject cannot be created without first consulting a forum that receives little input, and therefore a portal could not be created without a project backing it, all without a means for a project to get off the ground in the first place, it would amount to a [[vicious circle]] of automatically denying portal creation for some topics based upon the already largely broken system at the WP Council. :* Would older portals also be automatically, procedurally deleted if no project exists, or would this only apply to the newer ones, with a grandfather clause existent for the older portals? Either way, automatic deletion in this manner goes against several core principles of Wikipedia, and would serve to unnecessarily stifle the creation of functional, useful content. :* Regarding having discussions for all existing portals raised on talk pages of relevant Wikiprojects: this is very unlikely to even be viable. Who would ultimately be responsible to perform creating and then watching all of these discussions? Would said posited discussions be a subjective [[straw poll]], or based upon actual objective discussion about a portal's content and how it relates to a topic? Importantly, this would significantly and negatively shift Wikipedia from being a volunteer project to one that requires specific actions, in this case, mandatory discussions for all content in the portal [[Wikipedia:Namespace|namespace]]. This would set a very poor precedent for the encyclopedia. :* Regarding the notion of procedurally deleting portals if no consensus exists in a talk page discussion: at AfD, MfD, and other areas of deletion on Wikipedia, a no consensus result typically results in retention of a page or pages, rather than deletion. :*There's more, but I will leave my post at that for now. :– <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 12:16, 29 March 2019 (UTC) *'''Oppose''' I appreciate this proposal as one made in good faith using reasoned language. We should certainly invite WikiProjects to have more involvement in portals, including their creation and deletion. However, Northamerica1000 makes enough convincing arguments that I don't need to add any. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 13:40, 29 March 2019 (UTC) * Unfortunately reasonable, but WikiProjects do not own topics within their scope. (See also [[WP:CONLEVEL]].) --[[User:Izno|Izno]] ([[User talk:Izno|talk]]) 13:48, 29 March 2019 (UTC) * An [[Wikipedia talk:Portal/Guidelines#Portal creation and deletion criteria proposal 2|essentially similar proposal]] is under discussion at the portal guidelines talk page. The issue is not one of ownership; it's integrating support for portals with the same interested editors who maintain the navigation boxes and articles for the topic area. Particularly if the helper templates are used, editors need to take portals into account when modifying any associated navigation boxes and articles. But in general, portals can only be successful in the long term if they are supported in the same way as the rest of the related content. Accordingly, decisions on their creation and maintenance should be made by those editors, either under the aegis of associated WikiProjects, or through other methods of identifying editors active in the area. [[User:Isaacl|isaacl]] ([[User talk:Isaacl|talk]]) 15:11, 30 March 2019 (UTC) *If a portal is of high quality, it does not really matter whether there is a WikiProject about a related topic or not. Usually there will be a WikiProject (we have projects covering almost everything), but probably not a very active one. I do agree with the inviting subject experts to portal discussions, though. —'''[[User:Kusma|Kusma]]''' ([[User talk:Kusma|t]]·[[Special:Contributions/Kusma|c]]) 15:25, 30 March 2019 (UTC) *'''Support''' - I have tossed this out at [[WP:VPP|the Village Pump]]. See https://en.wikipedia.org/wiki/Wikipedia:Village_pump_(policy)#RFC:_Portals_and_Project_Sponsorship . [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 21:28, 30 March 2019 (UTC) *'''Comment''' - I will argue that NorthAmerica1000's argument about understaffed WikiProjects is a valid consideration that will serve as a check on the creation of rogue portals. (We are not discussing rogue WikiProjects here.) [[User:Robert McClenon|Robert McClenon]] ([[User talk:Robert McClenon|talk]]) 21:28, 30 March 2019 (UTC) *'''Comment''' – Wow, so <u>now we have two identical discussions occurring about the same topic in two different places</u>, now at '''[[Wikipedia:Village pump (policy)#RFC: Portals and Project Sponsorship|Wikipedia:Village pump (policy) § RFC: Portals and Project Sponsorship]]'''. As such, pinging all users who have participated here who have not commented at the new discussion, so their opinions here won't be lost or discounted at the new discussion: {{ping|Serial Number 54129|Thryduulf|Certes|Isaacl|Kusma}} Per the new discussion, I feel that '''this discussion should now be closed, with a redirect provided to the new discussion in the closure'''. <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 00:22, 31 March 2019 (UTC) **Three places, counting the discussion started in February at the portal guidelines talk page. [[User:Isaacl|isaacl]] ([[User talk:Isaacl|talk]]) 02:05, 31 March 2019 (UTC) * '''Request Closure''' This is at multiple venues and since this is not a proposal that ''affects administrators'' (specifically) this one should probably be procedurally closed. [[User: Crazynas|Crazynas]]<sup> [[User_talk:Crazynas|t]]</sup> 15:59, 1 April 2019 (UTC) ===Portal MfD Results=== {{cot|Some Portals closed at [[WP:MfD]] during 2019}} :Note: Struck the word "all" and added "(some)": this list is now incomplete. <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 11:47, 2 April 2019 (UTC) Grouped Nominations total 133 Portals (161 portals total): #[[Wikipedia:Miscellany for deletion/US County Portals]] '''Deleted''' 64 portals #[[Wikipedia:Miscellany for deletion/Districts of India Portals]] '''Deleted''' 30 Portals #[[Wikipedia:Miscellany for deletion/Portals for Portland, Oregon neighborhoods]] '''Deleted''' 23 Portals #[[Wikipedia:Miscellany for deletion/Portal:Allen Park, Michigan]] '''Deleted''' 6 Portals #[[Wikipedia:Miscellany for deletion/Portal:Airlines]] 4 Portals '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Cryptocurrency]] '''Deleted''' 2 Portals #[[Wikipedia:Miscellany for deletion/Portal:North Pole]] '''Deleted''' 2 Portals #[[Wikipedia:Miscellany for deletion/Portal:Winemaking]] '''Deleted''' 2 Portals Individual Nominations: #[[Wikipedia:Miscellany for deletion/Portal:Circles]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Fruits]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:E (mathematical constant)]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Burger King]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Cotingas]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Prostitution in Canada]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Agoura Hills, California]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Urinary system]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:You Am I]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Cannabis (2nd nomination)]] Reverted to non-Automated version #[[Wikipedia:Miscellany for deletion/Portal:Intermodal containers]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Adventure travel]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Adam Ant]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Benito Juárez, Mexico City]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Spaghetti]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Wikiatlas]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Greek alphabet]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Aleksandr Solzhenitsyn]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Accounting]] '''Deleted G7''' #[[Wikipedia:Miscellany for deletion/Portal:Lents, Portland, Oregon]] '''Deleted P2''' #[[Wikipedia:Miscellany for deletion/Portal:Ankaran]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Jiu-jitsu]] '''Deleted G8''' #[[Portal:University of Nebraska]] '''Speedy Deleted P1/A10''' exactly the same as [[Portal:University of Nebraska–Lincoln]] also created by the TTH #[[Wikipedia:Miscellany for deletion/Portal:Industry, California]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Ainu]] '''Deleted'''# #[[Wikipedia:Miscellany for deletion/Portal:Early human migrations]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Encarnación, Paraguay]] '''Speedy Deleted''' P2 #[[Wikipedia:Miscellany for deletion/Portal:English language]] '''No consensus''', redirected #[[Wikipedia:Miscellany for deletion/Portal:RuPaul's Drag Race]] '''Kept''' #[[Wikipedia:Miscellany for deletion/Portal:Nuclear technology/Intro]] '''Kept''' #[[Wikipedia:Miscellany for deletion/Portal:Derry]] '''Speedy deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Extraterrestrial life]] '''Speedy deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Marco Pierre White]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Yugoslavs]] '''Deleted'' #[[Wikipedia:Miscellany for deletion/Portal:LeBron James]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Spartacus]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Kirby]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Insomniac Games (2nd nomination)]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Equus (genus)]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Julius Caesar (play)]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Bede]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Tacitus]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Felix Mendelssohn]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Bill Cosby]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:R. Kelly]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:WWE]] '''Deleted''' #[[Wikipedia:Miscellany for deletion/Portal:Andrew Cuomo]] '''Deleted''' Related WikiProject: #[[Wikipedia:Miscellany for deletion/Wikipedia:WikiProject Quantum portals]] '''Demoted''' {{cob}} ====Discussion on MfD results==== :We get the message. 3% of portals, selected from the worst examples, have successfully been removed. I !voted to delete most of them myself. You are also working hard to get portal-related tools deleted while discussions on the project's future continue. However, AN is not the place to list every tiny victory in the War on Portals. This trophy cabinet is now full. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 13:15, 16 March 2019 (UTC) ::It's also worth noting that not all of these were deleted uncontroversially, so do not demonstrate a need for a speedy deletion criterion. This list, if you wish to maintain it, belongs in userspace. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 13:19, 16 March 2019 (UTC) :::This list is very relevant to a discussion about creating a CSD for similar pages. It provides an easy way for users to assess discussions unfiltered by opinions which go against community consensus. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 15:52, 16 March 2019 (UTC) ::*I agree with {{u|Thryduulf}}; the trophy case belongs in userspace. Furthermore, most of the pages deleted were from bundled nominations. However, at [[WP:MULTIAFD]], it states, "For the avoidance of doubt, '''bundling should not be used to form consensus around policy decisions''' such as "should Wikipedia include this type of article". Bundling AfDs should be used only for clear-cut deletion discussions based on existing policy." (Bold emphasis mine.) While WP:MULTIAFD technically applies only to articles, it comes across as an inappropriate list for this venue, where policy decisions are being discussed. <span class="smallcaps" style="font-variant:small-caps;">[[User:Northamerica1000|North America]]<sup>[[User talk:Northamerica1000|<span style="font-size: x-small;">1000</span>]]</sup></span> 19:59, 16 March 2019 (UTC) :::Yet this list was broadcast via the Portals Update #30 Newsletter. It can't be all that bad. No one wants to debate each neighborhood of Portland or each of the 723 Indian districts one by one. If someone listed a dozen very similar pages for debate there would be a lot of pushback to bundle them. Can we assume from these comments you insist on debating 4500 automated portals one by one? [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 02:57, 18 March 2019 (UTC) :::*Exactly that. This isn't one of our most frequently cited policies – mainly because attempts to do things that like that haven't been common since the early 2000s – but anyone deeply steeped in policy should already know it by heart, especially if they're big into deletion. Proposing major changes to deletion policy without actually understanding deletion policy is a competence failure. <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 21:05, 17 March 2019 (UTC) :::*{{replyto|Legacypac}} Bundling closely related discussions together is a Good Thing but completely different to using a bundled nomination of portals about 723 Indian districts to claim that there is consensus to speedily delete all single-page portals. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 14:45, 18 March 2019 (UTC) :::::As an Admin you really should be required not to post such misleading characterizations of what I said and the list of MfDs. The community deserves better than this. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 15:13, 18 March 2019 (UTC) ::::::While that is slightly more extreme than your position, I did not claim it was your position and it is far from being grossly misleading - certainly far less so than your mischaracterisations about what I am advocating for. This is particularly true as looking through the bundles, many are nowhere near as clear-cut as "Indian districts" - e.g. [[Wikipedia:Miscellany for deletion/Portal:Crabapples]] is quite likely to end as a trainwreck, and [[Wikipedia:Miscellany for deletion/Bottom Importance Portals]] is a clearly inappropriate bundling of unrelated pages. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 15:40, 18 March 2019 (UTC) *I think it should be reformatted and moved to our existing page, [[Wikipedia:Record of MfDs of portals]] (please see the bottom on how to correctly add the closes, this seems to have been neglected lately). —[[User:pythoncoder|<span style="border-radius:10px;background:blue"><span style="color:aqua"> python</span><span style="color:#ff0">coder </span></span>]] ([[User talk:pythoncoder|talk]] &#124; [[Special:Contribs/pythoncoder|contribs]]) 16:02, 27 March 2019 (UTC) ===Thousands of Autogenerated "Quantum Portals" with no human curation? === {{Archive top|Wikiproject will be demoted into the Portals project at MfD [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 06:38, 8 March 2019 (UTC)}} Discovered [[Wikipedia:WikiProject Quantum portals]] which I'm not sure I fully understand but looks like another big disruption brewing. Sent to [[Wikipedia:Miscellany for deletion/Wikipedia:WikiProject Quantum portals]] [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 04:54, 4 March 2019 (UTC) :Please note that in the case of quantum portals there would be no actual pages stored in Wikipedia, There would be a link which would create a temporary page which would exist only while it was open, and would disappear when closed, like a search result. Since they would only exist when someone actively invoked them, their existence would depend on them being seen as useful to the reader at the time. Some processing time would be necessary, currently this appears to be limited by technical constraints, and is the same as would be used for rendering an uncached article or saving an edit, so it is hard to see where massive disruption would come from. No maintenance would be required, other than occasional improvements to the script.&middot; &middot; &middot; [[User:Pbsouthwood|Peter Southwood]] [[User talk:Pbsouthwood|<sup>(talk)</sup>]]: 16:09, 4 March 2019 (UTC) ::[[User:Legacypac|Legacypac]] (or anyone else confused by this), see [https://tools.wmflabs.org/reasonator/ Reasonator] to get an idea of what they're talking about here. They don't serve exactly the same purpose—Reasonator assembles a pseudo-article in your browser on-the-fly based on data (which has no useful purpose on en-wiki, but it has an obvious potential use in more obscure languages, since it's less prone to errors than translation software)—but the principle is the same as that being discussed here. <p>I personally find the idea of a "quantum portal" beyond pointless, given that barely anyone uses even the real portals (something like [[Portal:Fish]] and [[Portal:Trains]]—both major topics with a high degree of world-wide interest and well over 100,000(!) incoming direct links—[https://tools.wmflabs.org/pageviews/?project=en.wikipedia.org&platform=all-access&agent=user&range=all-time&pages=Portal:Fish|Portal:Trains average around 20 and 80 views per day] respectively), but I can see that the theory behind it might make sense, especially for smaller Wikipedias where the category structure isn't as well organized and "show me a list of all the articles we currently have about trains, and all the train-related topics which other Wikipedias consider important but where we don't currently have an article" might actually be useful. <p>However, English Wikipedia is certainly not the appropriate testing ground for TTH to be conducting his experiments, especially given that [https://en.wikipedia.org/wiki/Special:PrefixIndex?prefix=Outline+of&namespace=0&hideredirects=1 we ''still'' haven't finished cleaning out the detritus from the previous time TTH tried to pull this "it's too late for you to stop me as I've already done it" stunt], let alone the most recent attempt with the portals.&nbsp;&#8209;&nbsp;[[User:Iridescent|Iridescent]] 11:06, 5 March 2019 (UTC) {{Archive bottom}} === Non-open drafting of an RfC about portals, and BHG behavior in relation to it === {{atop|reason=Irrespective of the previous misunderstandings or transgressions, it should be clear ''by now'' that # [[User:BrownHairedGirl|BHG]] does not wish to communicate with [[User:SMcCandlish|SMc]] on her own talkpage # She also does not welcome SMc's participation in the drafting (in her userspace) of [[User:BrownHairedGirl/Draft_RFC_on_Portal_criteria|a potential RFC]]. SMc may disagree with (2) being the best way forward but neither he nor any admin on this board can ''compel'' BHG to take SMc's input ''at this point''. SMc, of course, will have the opportunity to comment if/when the RfC does goes live; or, can draft an alternate RfC if he so wishes {{small|(not my recommendation, fwiw)}}.{{pb}} I don't believe it is a productive use of the community resources, or helpful to the editors involved, to dissect the minutiae of this recent dispute to judge who was more-in-the-wrong and apportion [[WP:TROUT|trouts]] (which, really, is the max "punishment" that any of this can merit). So I'll just ask SmC to respect BHG's wishes on the two points listed above and urge the two experienced editors to avoid addressing each other as far as possible over the next few days. [[User:Abecedare|Abecedare]] ([[User talk:Abecedare|talk]]) 07:13, 18 March 2019 (UTC)}} {{U|BrownHairedGirl}} and a few others she's hand-selected are drafting a proposed RfC about all of this. I have concerns about the non-open drafting of it. Its present wording is a train-wreck, and seems almost engineered to inflame dispute rather than resolve it (details [https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl&diff=next&oldid=888268699&diffmode=source here]). I also have behavioral concerns about BHG's over-control of this page and admin-unbecoming incivility and other behavior in regard to it. * I was directed to the draft and its talk page by BHG herself: "See [[User:BrownHairedGirl/Draft RFC on Portal criteria]] and its talkpage" [https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl&diff=next&oldid=888238090&diffmode=source]. **Not so. You were ''told about'' its existence. You were ''not invited'' to participate. (The distinction is not complicated. If I told you where me house is, that would not be an invitation to push your way in and make yourself at home).<br/> Your edits to that page were all made to a page which clearly warned you not to edit it. See e.g. the page when you made your first edit[https://en.wikipedia.org/w/index.php?diff=888262755&oldid=888261666&title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria]: a hatnote which said {{tq|This page is for discussion <u>by invitation</u> of the [[User:BrownHairedGirl/Draft RFC on Portal criteria]]. If other editors who wish to express views on the draft, please comment at User talk:BrownHairedGirl.}}, and below that a list of the editors who had been invited, and why.<br/>All open, transparent, striving for balance, and clear that you were not invited. I can only speculate whether you a) did not read it, or b) did not comprehend that plain English, or c) just chose for some reason to ignore it.<br/>The rest of SMcC's post below is similar nonsense: misrepresentations, half-truths, and flat-out malicious lies. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 06:00, 18 March 2019 (UTC) * After spending the time to do some policy analysis of this and to suggest revisions to most sections, it was all mass reverted by BHG [https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria&type=revision&diff=888266438&oldid=888266316&diffmode=source][https://en.wikipedia.org/w/index.php?title=User_talk%3ABrownHairedGirl%2FDraft_RFC_on_Portal_criteria&type=revision&diff=888266509&oldid=888266438][https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria&type=revision&diff=888266698&oldid=888266649&diffmode=source][https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria&type=revision&diff=888266819&oldid=888266698&diffmode=source][https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria&type=revision&diff=888266819&oldid=888266698&diffmode=source][https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria&diff=next&oldid=888266840&diffmode=source][https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria&diff=next&oldid=888266877&diffmode=source], on the grounds that I didn't have "permission" to comment there, despite being sent there by her, and despite others already replying to what I wrote [https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria&type=revision&diff=888265492&oldid=888265335&diffmode=source]. This kind of selective censorship does senseless violence to talk pages, not to mention the actual process of drafting this RfC. **No, you were ''not''{{tq|sent there}}. You were ''told about'' the page's existence. As above, there was a hatnote saying not to edit the page.[https://en.wikipedia.org/w/index.php?diff=888262755&oldid=888261666&title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria].<br/>Holding a discussion among a defined small group is not "censorship". It is a form of collaboration. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 06:17, 18 March 2019 (UTC) * She'd earlier said (though I did not notice it at the time) at her own talk page "I was quite happy to engage with you on the substance". She then censored all this substance anyway, with a demand that I put it on her regular talk page not the draft's talk page. **Yes, I did indeed write {{tq|I was quite happy to engage with you on the substance}}. But note that word "was"; it's past tense, to indicate that I am no longer happy to discuss with you.And note that SMcCandlish has dishonestly taken that phrase out of it context. My entire from which that is excerpted reads: {{tq|SMcCandlish a thoroughly bad faith comment like that bogus allegation that I get angry because my close is criticised marks the end of our discussion.<br/>I was quite happy to engage with you on the substance, but if you want to engage in that sort of smeary, twisted ad hominem, the discussion is over.<br/>Given that you ''agree'' that we need a consensus of criteria for portals, I really wonder what on earth was the point of this whole discussion.<br/>The RFC is not a public drafting process. I chose a small groups of people with differing views to facilitate quick progress. So the talk page is for that group only}}<br/> My edit summary was "enough".<br/>SMcCandlish's attempt to portray that as an invite to post on my pages is either [[WP:CIR]]-level reading comprehension problems, or a wilful attempt to mislead AN by dishonest trimming of a quote. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 06:17, 18 March 2019 (UTC) * Such a "you can't discuss it here" demand in itself is {{em|highly irregular}}. I can't think of any draft RfC in WP history with a talk page [[WP:OWN]]ed in this manner by someone. It'a also inconsistent with [[WP:TALKPAGE]] and [[WP:EDITING]]. **This is a [[WP:USERPAGE]], not a [[WP:TALKPAGE]]. See [[WP:REMOVED]].<br/>If and when the group completes the draft and move sit to apublic page, then you or anyone else can join in whatever discussion happens there. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 06:17, 18 March 2019 (UTC) * Whatever; I did as requested, and relocated all of this feedback [https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl&diff=next&oldid=888268699&diffmode=source] to BHG's talk page. I think it's important feedback, since since 5 of the 6 sections of the RfC draft are very problematic (several of the proposals are in direct conflict with policy and with ArbCom rulings, for example). **I did not request you to relocate anything to my talk page. I had already banned you from it.<br/>The edit summary which you quote below was a verbatim quote of the draft talk's hatnote, not a request or invitation. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 06:48, 18 March 2019 (UTC) **Whether or not there is any merit to your claim that your post contained {{tq|important feedback}}, that does not entitle you to impose it on another editor's talk page. You also seem to assume that you have some special insight into policy which is so critically important that you could not wait to present it either at the later public discussion of the draft, or at the RFC itself. If you genuinely believed that bizarre proposition to be true, then you should have taken care to behave with civility so that your comments would not be deleted unread. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 06:55, 18 March 2019 (UTC) * Despite having demanded it ("which part of "If other editors who wish to express views on the draft, please comment at User talk:BrownHairedGirl" was unclear to you???" [https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria&type=revision&diff=888266947&oldid=888266877&diffmode=source]), BHG then censored this version [https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl&diff=next&oldid=888269321&diffmode=source], too. Note in particular the uncivil edit summary: "you know perfectly well why you have been banned from my talk page. Now get lost". No admin should behave this way. **I did not {{tq|censor}} your post. I unread removed from my usertalkpage (see [[WP:REMOVED]]) a post from an uncivil editor who I had banned from my talk page for making a malicious and false allegation of bad faith.<br/>You know perfectly that you had been banned from my talk page because I honestly and fulsomely answered your questions about the close, you accused me of saying in effect[https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl&diff=prev&oldid=888261710] {{tq|"I get angry when when my closes are faintly criticized, and will spin implausible interpretations of what someone wrote just so I can vent".}}<br/>You chose to personalise a disagreement, and you chose to accuse me of "spin" and "vent". Those are accusation of bad faith, and they are conversation-stoppers in any context. I had given you my time to explain what I had done and why, and I am entitled to the very basic courtesy of not being accused of "spin" when I write a good faith explanation. <br/>It is risible of you to kill a conversation with your rudeness and your ABF, and then whine that you were told to "get lost". There is clear warning in my editnotice to assume good faith, not that it should be needed ... and when you have been asked no to post any more a * This is not actually a true claim; I had no idea BHG had "banned" me from her talk page until long after the fact, as I received no talk page notice about it. This apparently happened [https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl&diff=prev&oldid=888265554&diffmode=source here]; note the [[WP:ASPERSIONS]]: "maliciously false accusations of bad faith", which is pure projection, and accusing someone of malicious intent is a blatant [[WP:AGF|assumption of bad faith]]. (Last I checked, BHG doesn't have psychic powers and has no basis for assuming "malice" on my part; nor did I make any kind of accusation of bad faith toward her to begin with.) **The accusation of bad faith was made in your post of 00:22[https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl&diff=prev&oldid=888261710], in whch described my honest description of my close as {{tq| "I get angry when when my closes are faintly criticized, and will spin implausible interpretations of what someone wrote just so I can vent".}} <br/>You do not have to agree with my actions, or believe that my rationale is correct; but an an accusation of spinning "implausible interpretations" and of"venting" is an accusation of bad faith. It is demonstrably untrue, and can only have been made for malicious purposes.<br/>I made it very clear that I closed the RFC with a recommendation for a folowup portal-criteria RFC because the criteria were clearly unresolved and highly controversial. SMcC said in the same post {{tq|I agree that "editors need to build a community consensus on criteria for whether a portal should exist}} ... so all this querying of the close was all nonsense anyway: SMcC actually agreed with point he was contesting. Bizarre conduct. Was it baiting? --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 06:32, 18 March 2019 (UTC) * Importantly, the "ban" message has a timestamp of 00:53, 18 March 2019 (UTC), while every single demand BHG made, diffed in series above, to move my RfC-draft commentary to her main talk page came {{em|after}} that, and no such "ban" was mentioned in any of those demands. This is blatant [[WP:GAMING#Gaming the consensus-building process]] (it qualifies under at least 3 of the 4 points there), is [[WP:WIN]] behavior, and also an [[WP:ADMINCOND]] failure greater than the civility lapses and bogus aspersions. **Yet more hyerbolic nonsense. Writing a draft in userpsace is not a consensus-building process. It is a private discussion in userpsace. Nothing discussed on my draft page is any way binding on anyone or on any policy or guideline, unless several steps down the road it it is presented at an actual consensus-forming process and is adopted by consensus.<br/>Nothing in [[WP:ADMINCOND]] requires me to facilitate the repeated intrusions on my talk pages by an editor who has responded to my good faith WP:ADMINACCT explanations by making a malicious accusation of bad faith. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 07:03, 18 March 2019 (UTC) * What I {{em|actually}} did – what predicated all this weird behavior – was suggest that her strange reaction to my comments in user talk about one of her related closing decisions at this AN page seemed to me like a knee-jerk over-reaction to criticism. BHG's "ban" editsummary and wave of targeted censoriousness all being in response to {{em|that}} criticism (which I couched in terms of my own perception, not any allegations of intent) clearly proves the original point. It's the furthest thing from "maliciously false accusations of bad faith", but an accurate description of what's been happening. * It's not actually possible to "ban" people from your talk page, per [[WP:USERPAGE]] policy (at most, ignoring a request to stay away and instead using someone's talk page for unconstructive purposes will be used against you at ANI; nothing I've done here is unconstructive). Further, with BHG being an admin, [[WP:ADMINACCT]] applies. I'm entirely within my editorial rights to raise concerns about BHG's over-control, as an admin, of this RfC drafting, at her talk page. * As for the original close I constructively criticized: BHG clearly shouldn't be closing any of these discussions, being highly partisan and invested in the outcome. * I've attempted to make it clear that I'm actually in agreement with BHG that many of our portals do not need to exist, that there are maintenance costs associated with them, that an RfC is necessary, and that the community clearly does need to establish guidelines about them. I also reached out in e-mail, suggesting this was all just some mutual misunderstanding and "one of those days". This all seems to have fallen on deaf ears. {{strong|I don't think this RfC should be drafted inside a tiny echo chamber}}, especially when the output so far flies in the face of policy and ArbCom decisions. Either move the draft to "Wikipedia:" namespace and let everyone help shape it, or someone needs to draft a competing RfC that makes more sense. I think we all know from past experience that the former is a more productive process, though competing RfCs often nevertheless come to a clear consensus result.<br /><span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 02:42, 18 March 2019 (UTC) Oh, for God's sake. This a pile of timewasting utter nonsense from SMcCandlish, who appeared on my talkpage this evening spoiling for a fight, and got banned from my talk after a malicious and false accusation of bad faith ... and the disregarded the ban. Here's the facts. # I drafted an RFC offline and pondered what to do with it # I decided as a first step to try to form a small group of editors with divergent view to improve it, and then decide as group where to put the draft out for public reworking or launch it directly. # Every step of this was done on-wiki. # I chose two editors who thought broadly agreed with, and two who broadly disagreed with me. See it all at [[User talk:BrownHairedGirl/Draft RFC on Portal criteria]] # I did ''not'' invite SMcCandlish to comment on the draft. What I did write was {{tq|I am now working with a few other editors of varying viewpoints to draft an RFC which would try to set guidelines on which portals should exist. See [[User:BrownHairedGirl/Draft RFC on Portal criteria]] and its talkpage. }} [https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl&diff=888241839&oldid=888238090]. That is not an invitation # SMcCandlish's comments were posted to [[User talk:BrownHairedGirl/Draft RFC on Portal criteria]], which at the time of SMcCanslish's postings ahad aclear header saying {{tq|This page is for discussion by invitation of the User:BrownHairedGirl/Draft RFC on Portal criteria. If other editors who wish to express views on the draft, please comment at User talk:BrownHairedGirl.}} See that header present in the first post made there by SmcC[https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria&oldid=888262755]. Itw a sthere for all his other posts too, but I laer made it much promienent[https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria&diff=888267960&oldid=888267582] # I did not invite SmcC to join the group, because a) it was already formed; b) i had promised the group nom or invite without everyone's approval; c) SmCC had already on my talk been actively misrepresenting me, and I saw no benefit in bring a problem-maker into a problem-solving discussion # I ended the discusion on my talk with SmcC because of his conduct. SMcC had made malicious and false accusation that I was acting in bad faith[https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl&diff=888261710&oldid=888254260]: specifically that I {{tq|spin implausible interpretations of what someone wrote just so I can vent".}}<br/>In invited anyone interested to read the discussion above and see for themselves that there was no venting and no spinning. # I then hated the discussion, and banned SmC from my talk page[https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl&diff=888265554&oldid=888264896].<br/>There was no point in further engagement with SMcC, because if he genuinely believed that I was spinning and venting, that the discussion was clearly going nowhere; and if he was just hurling abuse, it was also going nowhere. # Only after closing that discussion did I see that SMcC had posted heavily on the talk page of my draft RFC. I then removed all his comments unread # I then saw a post on that draft page from another eidtor.@[[User:Legacypac|Legacypac]], who had written[https://en.wikipedia.org/w/index.php?title=User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria&diff=888266316&oldid=888265895] {{tq|If the User:SMcCandlish is going to be part of this working group I'm out of here. I have no interest in arging with their inability to be factual or analytical. Their comments should be removed so we can have a focused discussion.}}. I replied in agreement # I then found that SMcC had reposted his comments on my talk page, despite being asked not to do so. # I opened a discussion about his conduct at ANI, and then found I had just received an email from SmcC falsley claiming that I had been {{tq|"Gaming the consensus-building process": You invited my commentary, then nuked it}}.<br>Both blatant lies; I did not invite his commentary, and there was no gaming. # Then I found this pile of nonsense. He raised on my talk a legit question about my close, and I replied at length per [[WP:ADMINACCT]]. SMcC's response was to repeatedly misrepresent me, put words in my mouth, and then maliciously accuse me of bad faith ... and then falsely claim that I rescinded and invite which was never made, and ignore a very clear notice about a page he was asked not to post on. I have done nothing underhand here. I have created in my userapce a page [[User:BrownHairedGirl/Draft_RFC_on_Portal_criteria]] which cleraly sets out what I am trying to do; to collate all options, with a clear statement {{tq|note that my aim is to ensure that all options which may command support are presented here, and not to promote my preferences. If I have omitted any options, or given undue prominence to some, or included too many options, please treat that as unintended error by BHG, and propose a fix}}. If that is underhand or gaming the system, I am a banana. I have set out to draft this RFC in collaboration with 4 people, two of whom who I selected precisely because they disagree with me: see [[User_talk:BrownHairedGirl/Draft_RFC_on_Portal_criteria#Can_we_draft_a_joint_proposal]]. I explicitly say in hat section {{tq|My thinking is that if we can each consensus between us on the design of an RFC, then we could either * Launch the RFC as what we have designed, or * Take it to broader design discussion. I currently have have no preference on which of those paths to follow.}} I don't know why SmcC is behaving like this but their conduct this evening resembles that of an angry drunk looking for a fight. It is disagraceful disruption, timewasting, and a stream of malicious misepresentation. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 03:36, 18 March 2019 (UTC) :'''PS''' If there a strong feeling from others here that any draft produced by the we group we have assembled should be first taken to a public venue for further revision, then I for one would be very happy to do so. As I wrote long before SMcc appeared {{tq|Note that my aim is to ensure that all options which may command support are presented here, and not to promote my preferences. If I have omitted any options, or given undue prominence to some, or included too many options, please treat that as unintended error by BHG}}. The very last thing I wnat is an RFC which anyone feels in any way unfair, incomplete or otherise flawed. :However, I absolutely stand my decision that I do not want any further engagement with SmcC on my talk. As Legacypac wrote, {{tq|I have no interest in arging with their inability to be factual or analytical. Their comments should be removed so we can have a focused discussion.}} :The pile of malicious nonsense which SMcC has posted above merely confirms my judgement that SMcC would be a toxic and probably fatal wrecking factor in any attempt to collaborate. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 03:44, 18 March 2019 (UTC) :{{ec}} '''Update''': I didn't know it at the time, but BHG was drafting an ANI about me simultaneously: [[Wikipedia:Administrators' noticeboard/Incidents#User:SMcCandlish disregarding ban from my talk]]. I would think these should be merged, probably to this one since it's better diffed and raises more issues, including admin-specific ones and ones about community process. I'm going to bed now. I'll say three things before I do so: :* It's possible BHG may have believed I saw her "ban" note, saw her reverts and read their edit summaries, and kept posting to the same RfC talk page page just to spite her. It's not the case. I did my policy analysis of the RfC draft all in one go (though multiple saves), with single-minded focus. My monitor is something like 38 inches diagonal. The "you have a notice" icon is a very tiny blip at the far top right for me, and something I do not notice until I'm done editing and am looking around for what to do next; usually it's just the [[WP:FRS]] bot leaving "RfC spam" on my talk page, so I don't always look at the notices immediately even when I see that there is one. This quite possible to have escalated out of a one-sided misunderstanding, a misperception of someone else's editing and notice-checking habits. :* However, I can't see any kind of excuse for having "banned" me from her talk page then making repeated demands I take something from the draft RfC talk page to her talk page. It's flat-out GAMING. {{strong|You can't bait editors into "you {{em|can't}} use my talk page but you {{em|must}} use my talk page" traps and then try to ANI them over it.}} That [[unclean hands]] ANI report is a third ADMINCOND failure in the same "incident" (and such baiting actually resulted in a desysop before, though I won't name names, since the editor who did it took a break, returned, copped to it, and eventually got their admin bit back). And ever time BHG repeat the "malicious" accusation without any evidence of malice, and considerable evidence to the contrary, she's just [[First law of holes|digging her own hole deeper]]. :* All I really care about is a neutral, policy-compliant, sensibly worded RfC to arrive at a solid community consensus about when we should and should not have a portal. I don't think an RfC-drafting process controlled by one person can do that (especially given the [[WP:Writing policy is hard]] problems evidenced in the current draft, and double-especially when said owner shuts out constructive input because of an unrelated criticism they didn't like on another page). If you're going to draft an RfC and refuse others' input, don't advertise the RfC and it's talk page, FFS. It's another form of trap. While I've raised admin-behavior issues in the above, I don't expect or seek them to result in anything but an admonition, and am entirely willing to ignore the hypocritical "maliciously false accusations of bad faith" nonsense as long as it doesn't recur. I did finally hear back from BHG in e-mail (after both the ANI an this AN were open), and it just repeated the exact same assumption of malice. I objected to it again on AGF grounds, and will trust (AGF!) that this will be the end of it. Sorry this is long, but I'm done for the day and may not participate tomorrow due to off-site duties, so I need to make my case now all in one go. I'm not going to pore over all of BHG's even longer post above. My diffs show what they show. Timestamps don't lie. In skimming it, it looks like a bunch of "It was okay to do what I did because I was angry and thought I was being ignored" handwaving; it's {{em|not}} okay, and that's not an excuse.<br /><span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 03:48, 18 March 2019 (UTC) ::{{yo|SMcCandlish}}, more bad faith nonsense. ::# It is entirely reasonable of me to assume that an editor who is active posting on the pages of someone with whom they have had a disagreement reads their notifications. If you did not follow the notfication to stay off my talk page, that was your choice to ignore something pertinent. ::# I have engaged in no gaming and no baiting. That is yet more of your malicious nonsense. At no point did I invite your comments on the draft, and you posted the on a page which contained a very clear notice to post unless invited, with a list of who was invited which did not include your name ::#At no point did I {{tq|"banned" me from her talk page then making repeated demands I take something from the draft RfC talk page to her talk page}}. I quoted to you repeatedly the notice at the top of the age on which you had been posting uninvited. It did rescind the ban. ::#SMcC claims {{tq|If you're going to draft an RfC and refuse others' input, don't advertise the RfC and it's talk page}}. I did NOT advertise it; I mentioned its existence in one-to-one conversation, in the interests of transparency.<br/>If I told you where you my house is, would you interpret that as a license to push your way in past the notice saying "not unless uninvited" and then throw a tantrum if you were asked to leave? That is exactly what you did there. ::If you actually care about a {{tq|neutral, policy-compliant, sensibly worded RfC}} ... then please find within yourself the integrity to acknowledge that: ::* that is precisely the aim I set out at the top of the draft ::* That I have not acted unilaterally, and specifically asked editors to work me on the precise basis that they disagree with me. That is all set out publicly :: I do not actually believe your belated claim that your concern is about the RFC. What I see is a rude editor who repeatedly misrepresented what I had written, maliciously accused me of acting in bad faith ... and has now thrown the absolute mother of all bogus accusation FUD temper-tantrums because (surprise! surprise!) the editor who he maliciously accused of bad faith doesn't want to work with him. ::We are all volunteers here, SMcC. If you come to any editor or admin's talk, make outrageous and malicious allegations of bad faith, then do you really really expect to be asked to join a collaboration which had ''already'' been chosen to keep numbers low and views balanced? Really? Staggering sense of entitlement. ::Sleep it off, SMcC. --[[User:BrownHairedGirl|<span style="color:#663200;">Brown</span>HairedGirl]] <small>[[User talk:BrownHairedGirl|(talk)]] • ([[Special:Contributions/BrownHairedGirl|contribs]])</small> 04:58, 18 March 2019 (UTC) {{abot}} === Proposal 8: Help with unlinking === Now that the number of deletions is going up, I propose we use a bot to remove the resulting redlinks from the articles and templates that link to the deleted portals? Twinkle doesn't really do the job, because it leaves a non-link on the template or in the article's See also section, which doesn't really seem to make sense (see {{t|Agoura Hills, California}} for Twinkle's result). This will become important if the X3 proposal gains consensus. Absent that, I propose we get a dedicated group of editors to help with this task? It would be really nice if some of the members of [[WP:WikiProject Portals]] who created these (now red) links helped with the cleanup instead of taking their toys and going home. Ideas welcome. [[User:UnitedStatesian|UnitedStatesian]] ([[User talk:UnitedStatesian|talk]]) 06:37, 27 March 2019 (UTC) :Have you tried [[User:Evad37|Evad37]]'s [[User:Evad37/Xunlink.js|Xunlink.js|]]? It's better than Twinkle, though it too may leave the non-link on the template I guess. [[User:SD0001|SD0001]] ([[User talk:SD0001|talk]]) 05:13, 29 March 2019 (UTC) :I have removed the links which I created to portals which have since been deleted. The rest I will leave, to be part of the usual deletion process. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 16:34, 2 April 2019 (UTC) ::Thanks so much, appreciate it. [[User:UnitedStatesian|UnitedStatesian]] ([[User talk:UnitedStatesian|talk]]) 16:09, 3 April 2019 (UTC) :::The project members are too busy copy-pasting generic keep votes while they expect everyone else to come up with detailed unique rationals to delete on a portal by portal basis. The side project is opposing any bundling of noms. There is no time to handle boring jobs like removing links or fixing busted portals. [[User:Legacypac|Legacypac]] ([[User talk:Legacypac|talk]]) 16:42, 3 April 2019 (UTC) ::::Yes, the need to respond to hundreds of simultaneous copy-pasted MfDs is diverting many good editors from useful work. [[User:Certes|Certes]] ([[User talk:Certes|talk]]) 16:53, 3 April 2019 (UTC) :::::Look, because only one of the creators has objectively assessed the community's consensus and offered speedy U5 deletions, we have about 4,000 to discuss at MfD. Some will be kept, some will be deleted, but without pointing fingers we can all agree it is time consuming: will probably take about a year to get all the discussions completed. [[User:UnitedStatesian|UnitedStatesian]] ([[User talk:UnitedStatesian|talk]]) 17:44, 3 April 2019 (UTC) ::::::And taking that long is not a problem. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 07:30, 4 April 2019 (UTC) :::::::Of course not, having a year of tagging, discussion, back and forth, to debate the fate of thousands of creations which took one person a few hours to create obviously is ''not a problem''. Sending our readers to error-riddled, useless, uncared for portals is ''not a problem''. The only problem apparently is daring to question why ''these'' portals have to exist in the first place. Oh right, because of the content in them, which we should never delete but keep or merge. Except that they contain no content at all, of course, and there is nothing there to merge. [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 07:44, 4 April 2019 (UTC) ::::::::The portals should not have been created so quickly, but they were. Unless you have a time machine, that's not something you can change, so we have to work from where we are. Some of the portals should be deleted, some should be kept and some should be merged into broader portals - I have !voted all three ways on MfDs. Deleting those portals that should not be deleted will harm the project (in the same way that deleting anything that should not be deleted harms the project - which is why the ability to delete pages is restricted to administrators and speedy deletion is restricted even further), but no harm will come from those portals that should be deleted hanging around for a bit. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 08:27, 4 April 2019 (UTC) :::::::::You still persist with the nonsense that something ''can'' be merged here? Okay... How does it ''harm the project'' if pages which didn't exist for 15 years, contain no content not already available in the mainspace, are being used by very few people, and in many cases contain basic errors (like showing utterly unrelated pages, having large lua errors, duplicating an already existing portal, ...) get deleted ''without prejudice against recreation by a human''? At worst, deleting those upsets the editor who caused this whole sorry mess. While this is not the intention, it is hardly something I see as a valid argument against deletion here. On the other hand, these portals make us look even less efficient and trustworthy than we are, add clutter to articles, confuse readers (when we have e.g. two portals about the exact same topic), ... [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 11:21, 4 April 2019 (UTC) ::::::::::I have explained merging of portals to you multiple times in multiple places. That you disagree with me is fine, but just because you do does not make my opinion "nonsense", "disruptive", "incompetent" or any of the other derogatory and dismissive labels applied to it. The strong community consensus in the RfC is that portals (as a class, and good ones individually) are a net benefit to the project, therefore mass deleting them as a class and mass deleting individual ones that are of good quality removes that benefit. Anything that removes a benefit from the encyclopaedia obviously harms the project. Sometimes that harm is outweighed by other benefits deletion will bring but given that the best even the most argent opponents of portals can offer is that they can be undeleted later doesn't indicate any benefit to deleting them, let alone sufficient to outweigh the harm. Poor quality and duplicate portals are not ideal, but the same is true of articles (which are much more visible) and we don't speedy delete all new articles because some are poor or duplicates, instead we do such things as improve them, merge them, redirect them, and delete only those that are incapable of improvement or which duplicate better articles ''and'' have titles which are not suitable as redirects. That they make the project look less trustworthy is an argument I've seen advanced a few times over the years, but never with any evidence to back it up - it's an opinion, but nothing more than an opinion that (to my knowledge) has never been discussed anywhere to determine whether it is one shared by the community in general or not. [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 12:22, 4 April 2019 (UTC) :::::::::::I don't think I have used "incompetent" or "disruptive" (perhaps the latter, but that is one you have been using even in this very discussion to describe actions by others...). But you still haven't shown how you would merge a contentless page to another contentless page, you have just stated this (yes, repeatedly), but that doesn't make it any more true. "The strong community consensus in the RfC is that portals (as a class, and good ones individually) are a net benefit to the project, therefore mass deleting them as a class and mass deleting individual ones that are of good quality removes that benefit." There is no proposal here to mass delete the class of portals, the proposal, as you well know, is to mass delete the automated, careless, basically unsupervised creations by TTH. These are not "good quality" portals, these are either completely deficient ones or when luck has it portals which scrape by the minimum standards of the portal guidelines if one doesn't look to hard at all the requirements. Simply repeating that they are a benefit to the project, when so many of these have been shown to have no benefit at all, is not convincing. "we don't speedy delete all new articles because some are poor or duplicates, instead we do such things as improve them, merge them, redirect them, and delete only those that are incapable of improvement or which duplicate better articles ''and'' have titles which are not suitable as redirects." Except for those cases were it has become clear that too many articles from a creation batch (e.g. all articles by Sander v Ginkel, or some batches of 1000+ creations by Dr. Blofeld or Jaguar) were partly or completely wrong: in those cases, we deleted (or otherwise removed from mainspace) ''all'' these articles in one go, not after individual AfDs, because the percentage of problematic ones was too high, and the time needed to go through them one by one also too high. In those cases, we certainly deleted good (as in error-free) articles as well, but we did it anyway, and this was a good thing. Why we would take a different approach for pages which don't even contain new content (or any content), and where thus nothing is actually lost on deletion, is still not clear. [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 13:11, 4 April 2019 (UTC) :::::::::::{{ping|Thryduulf}} I see a statement in your last message that is so absurd that it requires a reply. You claim there is consensus that portals, as a class, are a net benefit to the project. The accurate state is that there is consensus that ''some'' portals are a net benefit to the project. And there is nothing to merge in unedited automated portals; I don't know if there is anything that can constructively be merged in ''edited'' automated portals. — [[User:Arthur Rubin|Arthur Rubin]] [[User talk:Arthur Rubin|(talk)]] 03:35, 6 April 2019 (UTC) ::::::::::::{{replyto|Arthur Rubin}} by "portals as a class" I mean that there is consensus that having portals at all is a benefit to the project. That doesn't mean that every portal is a benefit (I don't think anybody is arguing that, I'm certainly not), simply that "being a portal" is not a reason to delete (unlike, say, being a copyright violation, or being a template that misrepresents policy). In terms of merging, please see my explanations elsewhere (I don't have time right now to explain again - I shouldn't even be online!) [[User:Thryduulf|Thryduulf]] ([[User talk:Thryduulf|talk]]) 10:30, 6 April 2019 (UTC) :::::They don't ''need'' to respond to anything. The portals were ''created'' without even looking at them (as the basic errors in many of them attest to), they are not maintained, they are in most cases not used or appreciated by the readers, but you still feel the need to defend them because... well, why actually? If you want to do useful work instead of opposing the deletion of these portals, please do, no one is stopping you! [[User:Fram|Fram]] ([[User talk:Fram|talk]]) 07:38, 4 April 2019 (UTC) === Proposal 9 - delete the ones with no human improvements, leave the rest open to MFD === Human improvement can just mean that a bot checks whether anyone else has edited the portal. It could be announced a week or two in advance so if someone wants to preserve a particular portal, all they have to do is edit it. This is similar in spirit to the bot operation in the Darius Dhlomo (DD) CCI of a while back. DD had created around 10000 pages with suspected copyvios, and maybe 2000 of them were subsequently edited by other people. So after much discussion, someone launched a bot that blanked articles touched only by DD, leaving others for manual review. Obviously people should not wp:game the mechanism by editing portals without making improvements or having some other argument that the portal is worth keeping, particularly on large numbers of portals. [https://en.wikipedia.org/w/index.php?title=Wikipedia_talk:WikiProject_Opera&diff=prev&oldid=889691538 This comment] by Voceditenore shows how thoughtless the auto-creation was. [[Special:Contributions/173.228.123.166|173.228.123.166]] ([[User talk:173.228.123.166|talk]]) 18:54, 6 April 2019 (UTC) == HuffPost article on WP COI editing == Thanks to {{u|JamesG5}} I bumped into this HuffPost article of yesterday (or today depending on your timezone). It is dedicated to a particular COI editor on WP: * {{cite news | url=https://www.huffingtonpost.co.uk/entry/wikipedia-paid-editing-pr-facebook-nbc-axios_n_5c63321be4b03de942967225 | title=Facebook, Axios And NBC Paid This Guy To Whitewash Wikipedia Pages. And it almost always works. | date=14 March 2019 | publisher=[[Huffington Post]] | author=Ashley Feinberg}} Does it offer ideas for anything actionable? — [[User:Kashmiri|<span style="color:#30C;font:italic bold 1em Candara;text-shadow:#AAF 0.2em 0.2em 0.1em;">kashmīrī</span>]]&nbsp;[[User talk:Kashmiri|<sup style="font-family:Candara; color:#80F;">TALK</sup>]] 00:05, 15 March 2019 (UTC) *So long as he has disclosed and not directly edited pages, there's nothing we can do. If someone wants to change the policy to be stricter and prohibit it completely, I'll be the first to support, but I don't think we have that consensus yet (though I believe we eventually will. Also, note I'm talking about PR nonsense, not Wikipedians-in-residence, which is always a sticking point.){{pb}}I'll add that articles like this make us look ridiculous and that our official begrudging acceptance of disclosed paid editing is even more of a threat than undisclosed paid editing because it ruins our reputation when major media outlets runs stories like this.{{pb}}Finally, I'll put my 2¢ in that admins and others should not let declated paid editors do what I refer to as ''TOU bludgeon'': declaration is the minimum required to edit. It is not a free pass to spam. [[WP:NOTSPAM]] is still local policy and if someone openly declares themselves a spammer and the content matches, they should be indefinitely blocked without warning. [[Native advertising]] is very much a thing, and just because spam doesn't look like it did in 2005 when out policies were written, doesn't mean that our policies don't apply. [[User:TonyBallioni|TonyBallioni]] ([[User talk:TonyBallioni|talk]]) 00:14, 15 March 2019 (UTC) ::{{u|TonyBallioni}} I completely and passionately agree with your last paragraph. However, If a media organization wants to take issue with the calls we make on controversial topics they can and they will and we might not come out the otherside so great - they're tough areas for a reason. The fact that we have transparency means we can, if we want, revisit any of these editorial decisions. If there was no declaration those changes would be made and we wouldn't know or be any wiser and the community would have no option to re-evaluate the thinking. There are no good decisions for us to make here only least awful ones. Best, [[User:Barkeep49|Barkeep49]] ([[User_talk:Barkeep49|talk]]) 00:47, 15 March 2019 (UTC) :::Which headline makes us look more like fools: :::#''[https://www.bbc.com/news/technology-34127466 Wikipedia blocks hundreds of 'scam' sock puppet accounts]'' :::#''[https://www.huffingtonpost.co.uk/entry/wikipedia-paid-editing-pr-facebook-nbc-axios_n_5c63321be4b03de942967225 Facebook, Axios And NBC Paid This Guy To Whitewash Wikipedia Pages And it almost always works]'' :::The first headline is about Orangemoody. The second one is about someone following our TOU and policies. Anyone who has ever worked a day in a marketing department can tell you which headline they'd prefer.{{pb}}This is significant because we've fought for years to have our credibility accepted. I'm ''not'' saying that this is worse ethically than Orangemoody. Of course it isn't. I am saying that to the general public, this looks significantly worse. In Orangemoody, we were the heroes: fighting a bad guy scamming people out of their money. Here we are the bureaucrats that allow Big Tech to whitewash their own articles.{{pb}}Regardless of what the ''actual'' impact is on individual articles, the perceived impact is worse from declared PR editing, and that in turn makes all of the featured articles on notable topics that are extremely well researched worth less to the reader.{{pb}}I'm well aware that these are tough calls, but I'm saying that the community does need to consider perception here, and the perception from "white hat" editing on the outside is worse than some of our biggest sockfarms. I don't want an RfC on this now, but I do think it is something that is missing from community discussion on the topic, which is why I'm raising it. [[User:TonyBallioni|TonyBallioni]] ([[User talk:TonyBallioni|talk]]) 00:57, 15 March 2019 (UTC) ::::Not to detract from TonyBallioni's points, but just to answer one of the original questions of whether there is anything actionable, I didn't see such a thing. Problematic, sure. Actionable? Well, since the editor in question responds reasonably to comments, I don't see anything in particular right now. HuffPo also I feel is being a bit misleading. Regarding the Oppenheimer/Farrow thing, for instance, looking back, the section we had in his article was completely inappropriate for a BLP given what the sources actually stated. If what was previously written were verifiable, then those sources should have been added if the content was to stay like that. The wall-o-texts that HuffPo complains about don't seem big to me. And whether an article on a website needs to mention a criminal complaint against the founder is a completely ordinary coat rack discussion. Well, I guess '''CORPORATE PR PHONY WIKIPEDIA EDITOR WHITEWASHES ARTICLES''' is more compelling clickbait than '''Several companies pay Wikipedia editor to file routine boring complaints about content that arguably violates Wikipedia's own policies.''' [[User:Someguy1221|Someguy1221]] ([[User talk:Someguy1221|talk]]) 01:26, 15 March 2019 (UTC) ::::I don't think today's headline is worse for us than [https://medium.com/s/powertrip/wikipedias-top-secret-hired-guns-will-make-you-matter-for-a-price-a4bdace476ae Wikipedia’s Top-Secret ‘Hired Guns’ Will Make You Matter (For a Price)] and at least today we can decide if the changes really were policy compliant or not. Best, [[User:Barkeep49|Barkeep49]] ([[User_talk:Barkeep49|talk]]) 01:58, 15 March 2019 (UTC) :::::Damn, what's next? Soon they'll discover that I've been taking millions to edit Intel articles. THE JIG IS UP [[User:Drmies|Drmies]] ([[User talk:Drmies|talk]]) 02:06, 15 March 2019 (UTC) ::::::Millions?! I only get a few rubles! You need to hook me up.{{wink}} [[User:PackMecEng|PackMecEng]] [[User talk:PackMecEng|talk]]) 02:10, 15 March 2019 (UTC) ::::::Bedoel je niet wij, {{u|Drmies|goede dokter}} ;-). [[User:TonyBallioni|TonyBallioni]] ([[User talk:TonyBallioni|talk]]) 02:12, 15 March 2019 (UTC) :::::::Nice try, Tony, but that you are me (I?) is only a rumor on Reddit, and at any rate I AM NOT SHARING THE MILLIONS I GOT FROM INTEL FOR EDITING THAT ARTICLE WITH YOU. Damn I hope that that person who exposed me AS A PAID EDITOR FOR INTEL doesn't read this. [[User:Drmies|Drmies]] ([[User talk:Drmies|talk]]) 02:14, 15 March 2019 (UTC) ::::::::I once made a rather noncontroversial edit about compact fluorescent bulbs being more efficient than incandescent bulbs (this was before LED bulbs became affordable) and was accused of being "a paid shill for the Twisty Bulb Cartel". How did they guess? --[[User:Guy Macon|Guy Macon]] ([[User talk:Guy Macon|talk]]) 20:11, 5 April 2019 (UTC) *Back on point, while I agree the headline isn't great for Wikipedia, making policy in response to headlines is a slippery slope that I, for one, don't want to embark upon. Of course HuffPo is going to write the most sensational headline they can coin out of a relatively scant set of facts. I'm not really convinced that there is a lot in the story we should be worried about, which just leaves the headline. If you're looking for headlines critical of Wikipedia handling of material, there are plenty out there and they really do affect our credibility with a big section of the population; we shouldn't make policy in response to those headlines, either. [[User:GoldenRing|GoldenRing]] ([[User talk:GoldenRing|talk]]) 10:41, 15 March 2019 (UTC) ::The headline itself is useless, but the rest of the text could possibly be of use for those who want to take a look at the mentioned articles. [[User:Gråbergs Gråa Sång|Gråbergs Gråa Sång]] ([[User talk:Gråbergs Gråa Sång|talk]]) 12:05, 15 March 2019 (UTC) : Just to make it clear, we are talking about {{noping|BC1278}}--[[User:Ymblanter|Ymblanter]] ([[User talk:Ymblanter|talk]]) 15:21, 15 March 2019 (UTC) :: And I think the question the HP asks in our language would be whether their actions are compatible with [[WP:CANVASSING]].--[[User:Ymblanter|Ymblanter]] ([[User talk:Ymblanter|talk]]) 15:39, 15 March 2019 (UTC) :Hi. BC1278 here. Overwhelmingly, my Request Edits are made through a Request Edit flag. The format is usually very concise, as suggested by [[User: Spintendo]], a frequent reviewer to the Request Edit queue: e.g. [[Talk:Pace_University#Request_Edit]], [[Talk:Jonathan_Swan#Request_Edits]]. The "wall of text" complaint the author of the HuffPo column picked up on happened in an article about [[Noah Oppenheim]] during extended discussions about controversial issues with multiple RfCs. The consensus decisions ultimately reached by independent editors were not remotely like my original proposed edits, as the HuffPost author falsely implies. Instead, independent editors did their job and came to their own conclusions. One outcome of participating in a couple of these very contentious discussions was a chat last year with DGG, who advised me that he had learned over the years there's very little advantage in getting involved in debates after you've made your point once - you're not going to convince people to change their minds anyway. I have tried to adopt his style since. The HuffPost column is focused on a few high-profile media-related Wikipedia articles which involved public controversies (the author's beat), rather than how I conduct myself on Wikipedia in general. It's click bait. It is also rife with mistakes and misleading statements too numerous to explain here. I am going to ask for HuffPo for multiple corrections. For example, she ignores that I was the editor who suggested expanding into a robust paragraph, the few words mentioning the Matt Laeur firing on [[NBC News]], despite the subject being very unflattering to them. But I wanted the NBC News article to be up to date anyway. The HuffPo author cherry picked one sentence she didn't like in my proposed edit, even though, as per a normal independent review, another editor chose to use entirely different language than anything I submitted (and I added words of encouragement, saying it was well done.) [[Talk:NBC_News#Expanded_info_on_Matt_Lauer]] Her example of alleged canvasing are notifications to editors who had already participated in extended discussions on [[Talk:Noah Oppenheim]] that more discussions were continuing in a new RfC. If she looked carefully, she would have seen that I notified (or tried to) all the recent editors, including those who opposed my proposals previously, such as [[User: Peter K Burian]]. This was my first RfC and to me, there appeared to already be consensus, when JytDog re-opened the question as a new RfC. I thought the previous editors discussing the same matter should be notified again. Today, having been through a few, I would have added all the notifications right on the RfC page, to be transparent, and let others double check I didn't mistakenly leave anyone out. Or, to be honest, I just wouldn't bother to notify anyone - at the time, I didn't know how RfC editors were even called upon.[[User:BC1278|BC1278]] ([[User talk:BC1278|talk]]) 20:36, 15 March 2019 (UTC)BC1278 ::FYI, if you'd like to know what its like to field inquiries from prominent organizations, PR firms or individuals who think articles about them have problems, or want a new article, many balk when I tell them how I work - with full disclosure of COI as a paid editor and submitting all suggested edits for independent review. They don't want to take the risk of appearing in articles like the one by HuffPo. So I turn down their business, as my entire premise is that I do "white hat" work, only for those who want to follow the rules. Sometimes, a few months or a year down the road, I check to see if the articles of those who chose not to work with me nonetheless were edited or published as they wanted -- and it's usually the case they have been, but never with a public disclosure of COI or prior review. As the editing is anonymous, I can't be sure what happened, of course. I do know it will be more difficult to get subjects to publicly disclose because of this article, but it won't slow down the organizations/individuals from violating Wikipedia policy and making direct edits. Not in my experience. Only a much more radical change will solve the problem -- for example, the elimination of anonymous editing, with all user accounts requiring a LinkedIn profile. Then, COI and agenda editing will be more obvious. It would also go a long way toward solving the civility issues. But given the sanctity of anonymous editing on Wikipedia, I guess it isn't viable.[[User:BC1278|BC1278]] ([[User talk:BC1278|talk]]) 20:36, 15 March 2019 (UTC)BC1278 :::Hi guy here who thinks you're right that UPE is worse. But do you understand why as a volunteer how your 700+ words are troubling and could be seen as [[WP:BLUDGEON]]ing this conversation in contradiction of [[WP:PAYTALK]]. Best, [[User:Barkeep49|Barkeep49]] ([[User_talk:Barkeep49|talk]]) 21:33, 15 March 2019 (UTC) ::::Yes. Sorry/ I re-read it a bunch of times to try to cut it. But I'm responding to a major press article that made a slew of misleading and inaccurate statements about me, personally, and that now seems to be swaying discussion on Wikipedia policy itself. For four years, I've worked to convince organizations and PR firms to abide by COI disclosure rules because that's what Wikipedia has decreed is kosher Someone from the Wikimedia Foundation needs to publicly stand up to this young media reporter who thinks UPE is more ethical than declared PE or declared COI editing. That's what this author is explicitly saying! I received calls and emails from major PR agencies all day -- if this is the new normal, they're going to direct business away from the "white hats." There are board meetings taking place next week to formalize this, affecting some of the largest corporations in the world. Unless something changes, the outcome will be a lot more business for "black hats."[[User:BC1278|BC1278]] ([[User talk:BC1278|talk]]) 04:00, 16 March 2019 (UTC)BC1278 :::::I don't agree with this. UPE is bad, and we obviously need to root it out when we can; but I feel that Wikipedia is large enough now that the damage it can do is ultimately containable. Declared paid editing, on the other hand, hurts Wikipedia's reputation by making it seem as though we don't care about the potential issues raised in articles like this one at all. And, more generally - "if you ban this, people will just evade and do it anyway" has not, I think, generally been a strong argument for anything. People get away with violating all sorts of policies. (I would also add, as I mentioned down below, that I feel that the nature of paid editing and the confusion over it allows paid editors to get away with clearly [[WP:TENDENTIOUS]] editing that would get a normal editor in far more trouble, since people feel that that one-sided editing is "expected" from them. An undisclosed paid editor cannot devote the same intensity, passion, and time that you have brought to your work here, since it would attract attention, opposition, and, eventually, sanctions.) But more generally you're ''not wrong'' that everyone has POVs and that most tendentious editing goes unsanctioned - the really serious problem for disclosed paid editing is the damage it does to Wikipedia's reputation, which I feel is, today, a more serious problem than any other aspect of the issue. --[[User:Aquillion|Aquillion]] ([[User talk:Aquillion|talk]]) 01:52, 5 April 2019 (UTC) *I mean, yeah, it does. BC1278 is alleged to be a serial POV-pusher and professional whitewasher, who [[WP:GAME|games the system]] to get his edits through with a combination of relentless [[WP:BLUDGEON|bludgeoning]] and [[WP:CANVASS|canvassing]]. That's extremely alarming and I was ready to crucify this guy. I was even pissed to see the lighthearted reactions above. But, when you actually examine the article, I'm not seeing any violations. In fact, I'm not really seeing anything of major concern. The article itself seems to quietly concede that he doesn't actually violate any policies. In fact, it comes across as extremely misleading and obviously written by someone who doesn't understand Wikipedia at all. He "spent over a year lobbying" for the creation of [[Caryn Marooney]]? Come on, he created it as a draft and got it approved through the AfC process, not because he's some relentless lobbyist. Relentless bludgeoning, based on [[Talk:Noah_Oppenheim#RfC on decision to let Weinstein story go|this]]? Really? He's literally just discussing something in the discussion section, because he was refraining from !voting. Obviously the writer has never witnessed true bludgeoning. Canvassing? The supposed incidents of "canvassing" are usually explained as simply being notifications to relevant users who are involved in some way, such as WikiProject members. I have not seen any refutations of that point. I mean, one of the warnings cited was literally for notifying the ''only'' other contributor to an article about a deletion discussion.[https://en.wikipedia.org/w/index.php?title=User_talk%3ALake_Ontario_Wind&type=revision&diff=844905493&oldid=841064608] There's nothing even particularly unreasonable about that. Most of the supposed "whitewashing" seems to be mundane matters that don't harm articles at all, if not actual improvements, like making articles better comply with BLP. "It almost always works"? Uh, yeah, if you're in compliance with policies and are making reasonable requests that are being vetted by established editors who decide to approve them, then good for you, you're not terrible at what you do. It certainly isn't because the community has no problem with paid COI editors, on the contrary, they're among the most stigmatized editors within the community. This article seems to be little more than an unfortunate piece of trumped-up clickbaity garbage, and I actually feel bad for the paid editor here. I hope both the editor and the Foundation will push back in some way. If COIN wants to do an in-depth investigation of this editor, that's perhaps a reasonable reaction, but based solely on the allegations and supporting evidence presented in the article, which, I assume was the worst they could find, there's nothing actionable there. [[User:Swarm|<span style="color:Green">'''~Swarm~'''</span>]] [[User talk:Swarm|<span style="color:DarkViolet">'''{talk}'''</span>]] 21:02, 16 March 2019 (UTC) ::{{u|Swarm}} I have spent some time examining this user's editing. I think on the whole I agree with your analysis. But even in that rather long analysis above you're still about 55% as verbose as {{u|BC1278}} is in his response here. I think given PAYTALK, which I value as a volunteer editor, he could learn how to be more concise. The problem with him at Oppenheim, as I see it, isn't with the RfC, it's with what came [https://xtools.wmflabs.org/topedits/en.wikipedia.org/BC1278/1/Noah_Oppenheim before]. Similar verbose behavior can be seen at other of his [https://xtools.wmflabs.org/ec/en.wikipedia.org/BC1278#top-edited-pages pages]. I compare that to [https://en.wikipedia.org/wiki/Special:Contributions/VZBob this paid editor] who accomplishes their work in a far more concise manner. But to emphasize I think that the HuffPo article, like much of the media commenting on Wikipedia practices, gets things wrong, and in this case does so with a clear agenda in mind. Best, [[User:Barkeep49|Barkeep49]] ([[User_talk:Barkeep49|talk]]) 22:24, 16 March 2019 (UTC) :*I think that that's a misreading of the article, which is clearly written from the perspective that all paid editing is inherently problematic and that our policies allowing it are the core issue here. Obviously people here disagree on that, but it's not a reason to ''disregard'' the source - I don't think there's anything inherently wrong or questionable about positing that paid editing, even by someone who follows all our rules, might unbalance articles due to the disparate levels of energy and time devoted. (Although the article doesn't say this, I think it's also worth pointing out that the nature of Wikipedia has changed a lot since we originally decided to allow paid editing, generally in ways that make it more problematic - controversies over low-to-mid-tier articles are more likely to get hashed out on talk pages in general, say, which makes many of the restrictions we place on paid editors moot and calls into question whether the image problem they create for the project is worth what we get by having them declare themselves instead of inevitably just evading successive bans.) --[[User:Aquillion|Aquillion]] ([[User talk:Aquillion|talk]]) 01:16, 5 April 2019 (UTC) :I think that there is a "money is bad" mentality that induces people into writing articles of debatable accuracy about paid editing on Wikipedia. In a way it's similar to the POV-pushing process. I agree that the "bludgeoning" there isn't, plenty of people write mildly detailed arguments. And if memory serves this would be far from the first time where a news article about Wikipedia has turned out to be partially or mostly wrong. Some caution is due before citing newspaper articles about Wikipedia as arguments for a policy change or on-wiki action. [[User:Jo-Jo Eumerus|Jo-Jo Eumerus]] ([[User talk:Jo-Jo Eumerus|talk]], [[Special:CentralAuth/Jo-Jo Eumerus|contributions]]) 21:16, 16 March 2019 (UTC) * Thanks {{u|Swarm}} for taking time to go through the edit history and this way answering my original question. : As to COI editing, {{u|Jo-Jo Eumerus}} has put it right. We often distrust those who have vested financial interest in what most of us are doing for free, ''ergo'', in our view, selflessly. : Hopefully, in the longer run, common sense will prevail. Maybe a day will come when for example we will allow company infoboxes to be edited by company staff, or person infoboxes by article subjects. Until we find an open and transparent way of managing COI, we will see articles like the HuffPost piece. — [[User:Kashmiri|<span style="color:#30C;font:italic bold 1em Candara;text-shadow:#AAF 0.2em 0.2em 0.1em;">kashmīrī</span>]]&nbsp;[[User talk:Kashmiri|<sup style="font-family:Candara; color:#80F;">TALK</sup>]] 00:44, 17 March 2019 (UTC) ::{{u|Swarm}}, {{u|Barkeep49}}, {{u|Jo-Jo Eumerus}}, {{u|Kashmiri}}, {{u|Ymblanter}}, {{u|GoldenRing}}, {{u|TonyBallioni}}, {{u|PackMecEng}}, {{u|Drmies}}, [[User:Gråbergs Gråa Sång|Gråbergs Gråa Sång]] and anyone I missed here: Given the subject of the Request Edit here [[Talk:Caryn_Marooney#section=1]] and the already removed language from [https://en.wikipedia.org/w/index.php?title=NBC_News&diff=887925487&oldid=887923833 from NBC News] (editors using this HuffPo article to include accusations of Wikipedia impropriety in the WP articles about the organizations mentioned), would it be possible for an official consensus as to whether this article is or is not a reliable source for alleging paid editing impropriety such that it can be included in the Wikipedia mainspace articles about or related to the organizations highlighted in HuffPo? Or, whether the article is reliable in general? This is going to repeat over and over.[[User:BC1278|BC1278]] ([[User talk:BC1278|talk]]) 23:10, 17 March 2019 (UTC) :::I'd say it's reasonably reliable for ''alleging'' (by which I mean "according to HuffPost" or whatever) paid editing impropriety, but will currently probably fail on [[WP:UNDUE]]/[[WP:NOTNEWS]] (and maybe [[WP:BLP]], depending on use) aspects. I was thinking of [[Conflict-of-interest_editing_on_Wikipedia#Miscellaneous]], but it seems a little weak on it's own. HuffPost is not ''Daily Mail'', but it's not ''Washington Post'' either. [[User:Gråbergs Gråa Sång|Gråbergs Gråa Sång]] ([[User talk:Gråbergs Gråa Sång|talk]]) 07:59, 18 March 2019 (UTC) ::*{{ping|BC1278}} In response to your request for {{tq|"an official consensus"}}, I've started a discussion on the reliable sources noticeboard at {{slink|WP:RSN|HuffPost for paid editing at Axios (website), NBC News, Caryn Marooney, and other articles}}. —&nbsp;'''''[[User:Newslinger|<span style="color:#536267;">Newslinger</span>]]'''&nbsp;<small>[[User talk:Newslinger#top|<span style="color:#708090;">talk</span>]]</small>'' 17:51, 2 April 2019 (UTC) * Speaking of [https://en.wikipedia.org/w/index.php?title=Talk%3ACaryn_Marooney&type=revision&diff=888251819&oldid=887952971 WP:BLUDGEON and WP:PAYTALK...] [[Special:Contributions/2600:6C44:E7F:F8D6:8694:953B:9EC1:FBC|2600:6C44:E7F:F8D6:8694:953B:9EC1:FBC]] ([[User talk:2600:6C44:E7F:F8D6:8694:953B:9EC1:FBC|talk]]) 04:27, 18 March 2019 (UTC) :*If you're implying that's an example of "bludgeoning", then no. In fact, based on the above, the user presents a perfectly reasonable case. If anyone is unclear on what "bludgeoning" looks like, check out the discussions I collapsed at [[Talk:Origin of the Romanians/Archive 18]]. If you're really a glutton for punishment, keep scrolling past that. Eventually, you may reach the bottom of the page. [[User:Swarm|<span style="color:Green">'''~Swarm~'''</span>]] [[User talk:Swarm|<span style="color:DarkViolet">'''{talk}'''</span>]] 20:14, 18 March 2019 (UTC) :::Maybe you missed the part of those 572 words where he asserted AN consensus that HuffPo is not a reliable source? That's a misrepresentation at best, and the whole thing is a classic example of throwing shit at the wall to see what sticks. [[Special:Contributions/2600:6C44:E7F:F8D6:8694:953B:9EC1:FBC|2600:6C44:E7F:F8D6:8694:953B:9EC1:FBC]] ([[User talk:2600:6C44:E7F:F8D6:8694:953B:9EC1:FBC|talk]]) 01:40, 19 March 2019 (UTC) === Suggestions and proposals related to [[WP:PAID|paid editing]] === *We should at least say "paid editors are not to directly edit articles"... Even info boxes maybe problematic as they try to exaggerate the number of employees ect. [[User:Doc James|<span style="color:#0000f1">'''Doc James'''</span>]] ([[User talk:Doc James|talk]] · [[Special:Contributions/Doc James|contribs]] · [[Special:EmailUser/Doc James|email]]) 17:11, 20 March 2019 (UTC) *This part concerns me: :{{tq2|Posts calling attention to Sussman’s [[Special:Diff/843020422#Choice of editors to move your draft articles.|lobbying of other editors]] rarely stay up for more than a week. According to his Talk page history, Sussman deletes criticism frequently and any record of it in his user logs often gets buried by his [[Special:Contributions/BC1278|prolific posting and editing]].}} :Should paid editors be restricted from deleting other editors' comments from their user talk page? Combing through a [https://en.wikipedia.org/w/index.php?title=User_talk:BC1278&action=history history like this] is unnecessarily arduous, and the status quo hinders oversight from other editors by allowing important discussions to be obscured. —&nbsp;'''''[[User:Newslinger|<span style="color:#536267;">Newslinger</span>]]'''&nbsp;<small>[[User talk:Newslinger#top|<span style="color:#708090;">talk</span>]]</small>'' 10:25, 25 March 2019 (UTC) ::I don't see this incident going away anytime soon. A new discussion was started at ANI just today: [[Wikipedia:Administrators'_noticeboard/Incidents#Whitewashing?]]. What I find most offensive to those of us who edit for free, and worse, what may prove damaging to WP in the long term, are sites like [http://whitehatwiki.com/ this one] and the claims they make while marketing their business. I don't know how long volunteers can be expected to keep working for free in order to make an article encyclopedic and compliant with our PAGs knowing it's for the benefit of paid editors. Think about that for a minute. Our own paid editing/COI PAGs lack common sense. So paid editor John Doe gets a nice check for $400+/- (probably a great deal more if worth their salt) to write/protect an article but unpaid editors are actually the ones writing the article for them. How is this not insanity? [[User:Atsme|<span style="text-shadow:#F8F8FF 0.2em 0.2em 0.4em,#F4BBFF -0.2em -0.3em 0.6em,#BFFF00 0.8em 0.8em 0.6em;color:#A2006D"><sup>Atsme</sup></span>]] <sub>[[User talk:Atsme|<small>Talk</small>]]</sub> [[Special:EmailUser/Atsme|📧]] 00:49, 27 March 2019 (UTC) :::It's insane that Wikipedia's rules are that you cannot be paid to edit an article, you can only be paid to get unpaid volunteers to edit the article for you. [[User:Levivich|Leviv]]&thinsp;<span style="display:inline-block;transform:rotate(45deg);position:relative;bottom:-.57em;">[[User talk:Levivich|ich]]</span> 02:10, 27 March 2019 (UTC) ::::I highly agree with [[User:Atsme]] and [[User:Levivich]]. Why should volunteers edit an article for someone getting paid wads, while us volunteers get paid nothing at all? While I understand that we've opted to keep ''some'' COI editing aboveboard instead of outlawing it and just driving paid editing underground, paid editing is still highly problematic. [[User:CaptainEek|<span style="color:#6a1f7f">'''Captain Eek'''</span>]] <sup>[[User talk:CaptainEek|<span style="font-size:82%"><span style="color:#a479e5">''Edits Ho Cap'n!''</span></span>]]</sup>[[Special:Contributions/CaptainEek|⚓]] 03:55, 27 March 2019 (UTC) :::::I don't begrudge the way any person makes an honest living, and if a paid editor is complying with policy, they're doing nothing wrong in my book. The policies are kafkaesque, but that's the inevitable result of trying to police editors instead of edits. <span style="display:inline-block;position:relative;white-space:nowrap;">[[User:Levivich|Leviv]]&thinsp;<span style="transform:rotate(45deg);bottom:-.57em;">[[User Talk:Levivich|ich]]</span></span> 04:39, 27 March 2019 (UTC) ::::::Nor do I, Levivich, but it's wrong to do it at the expense of volunteers who are committed to building a free knowledge-based encyclopedia. The marketing material of companies like [http://whitehatwiki.com/ White Hat Wiki] is an insult to everything WP represents. Phrases like ''"We Bullet-Proof Your Wikipedia Presence"'', and ''"Wikipedia is a byzantine labyrinth of policies, guidelines and internal politics"'' are far from flattering to the project and its volunteers. Paid editing changes the landscape and the very definition of ''knowledge-based encyclopedia'' and converts it to a ''Whose Who in business''. Catch phrases like {{xt|"We use sophisticated strategies and our knowledge of the complex rules to get results}} is an insult - "get results"?? And what results might that be? When a company is notable enough to be included in WP, a volunteer (typically patrons or fans) will eventually write the article. To do otherwise weakens the very foundation WP is built on. I can't help but wonder how much money paid editing actually diverts away from {{u|Jimbo Wales|Jimbo's}} fund drives and the much needed contributions that keep this project alive. Why should companies contribute to WMF when they're paying an independent company to write/oversee their articles? I truly believe this is something WMF needs to carefully reconsider, but I'm only one voice. Perhaps the time has come for WMF to pay its own select group of qualified editors to work exclusively on business/corporate articles, and keep that money going to the project instead of independent companies, unless the goal is to grow, support and protect the cottage industries that are sprouting up around us. I shudder to think all the time and energy that is being devoted to COI by editors like {{u|Doc James}} and the volunteers he's worked with is for naught, or worse, driving COI editors to become/work with independent companies at the expense of other WP volunteers. [[User:Atsme|<span style="text-shadow:#F8F8FF 0.2em 0.2em 0.4em,#F4BBFF -0.2em -0.3em 0.6em,#BFFF00 0.8em 0.8em 0.6em;color:#A2006D"><sup>Atsme</sup></span>]] <sub>[[User talk:Atsme|<small>Talk</small>]]</sub> [[Special:EmailUser/Atsme|📧]] 12:03, 27 March 2019 (UTC) *This "Should paid editors be restricted from deleting other editors' comments from their user talk page?" by [[User:Newslinger]] is an excellent suggestion. They can use automated archiving but Talk pages are here to improve Wikipedia so they do not belong to any single editor. [[User:Doc James|<span style="color:#0000f1">'''Doc James'''</span>]] ([[User talk:Doc James|talk]] · [[Special:Contributions/Doc James|contribs]] · [[Special:EmailUser/Doc James|email]]) 12:14, 27 March 2019 (UTC) If I were making the rules here, I'd require all new corporate articles to be moved immediately to draft space and EC-protect the creation of each title in main space, forcing each new corporate article to go through review. If the paid editor has to wait for it, that isn't our problem. If disclosed paid editors complain, that also isn't our problem. I would also EC-protect any approved/established corporate article in main space, to force the PR folks to request changes on the talk page. These rule changes wouldn't have any effect on long-term paid editors with a long contribution history, but this would likely eliminate a lot of the undisclosed paid crap. I mean, we have these tools already, let's stop whining about the situation and use them. ~[[User:Anachronist|Anachronist]] <small>([[User talk:Anachronist|talk]])</small> 04:40, 28 March 2019 (UTC) *How come this guy hasn't been blocked indef? It is most detestable and ''infuriating'' to have the fruits of our volunteer labor ripped by these paid editors walking away with swathes of cash. Another second that these parasites are accomodated here is an insult to us all. Concur with talk page post removal restrictions at the very least. [[User:Tsumikiria|<span style="font-family:'Lato',sans-serif;color:Crimson;text-shadow:2px 2px 12px HotPink;">Tsumikiria''⧸''<small> </small></span>]][[User talk:Tsumikiria|🌹]][[Special:Contributions/Tsumikiria|🌉]] 19:49, 28 March 2019 (UTC) *: Because he hasn't broken any policies? We cannot and will not simply block someone because you don't like what they do. This was not a ban discussion, by the way; it's a discussion about a HuffPo article. [[User:Jo-Jo Eumerus|Jo-Jo Eumerus]] ([[User talk:Jo-Jo Eumerus|talk]], [[Special:CentralAuth/Jo-Jo Eumerus|contributions]]) 20:20, 28 March 2019 (UTC) *::Is that actually true? I'm aware of [[Wikipedia:Paid-contribution disclosure]], but [[WP:NPOV]] and [[WP:TENDENTIOUS]] still apply. It seems to me that paid editors are in constant danger of falling afoul of those policies, since if their services go even a hair beyond "generally improve Wikipedia on this topic", they are not here to make neutral edits or simply to build an encyclopedia - they are here to represent the POV they've been paid to represent. As far as I'm aware, the tension between what [[Wikipedia:Paid-contribution disclosure]] allows and what [[WP:NPOV]] and [[WP:TENDENTIOUS]] disallow has never been properly resolved. But [[WP:NPOV]] and [[WP:TENDENTIOUS]] are absolutely policies; a paid editor is subject to them just as thoroughly as anyone else. I feel this article makes a reasonable argument for tendentious editing in particular. If what an editor is ''doing'' is [[WP:POV]] or [[WP:TENDENTIOUS]] editing, then clearly it's a concern (and I feel that some editors have allowed "paid editing is allowed, under certain circumstances" to blind them to that fact.) EDIT: On reflection, I think that most paid editing is also a violation of [[WP:NOTHERE]], especially the point forbidding editors from edits that are {{tq|trying to score brownie points outside of Wikipedia}}. An editor trying to maintain the favor of their employer is the purest representation of that sort of [[WP:NOTHERE]] behavior imaginable. --[[User:Aquillion|Aquillion]] ([[User talk:Aquillion|talk]]) 01:31, 5 April 2019 (UTC) *:Unfortunately, most people interpret [[Wikipedia:Paid-contribution disclosure]] as allowing paid editing; at least, it has never been formally banned (though I think many parts of [[WP:NPOV]], [[WP:TENDENTIOUS]], and [[WP:NOTHERE]] make it dubious in most practical cases, including this one.) Either way, I feel that a lot of people underestimate the harm that that does to the project, but that's how things are at the moment. If you want to help, one thing to do is to start pushing more firmly for an unambiguous ban on paid editing; but absent that, you can also spend time reviewing past work by paid editors and challenging things that seem questionable. It might also be worth considering a Wikiproject devoted to reviewing suggestions by paid editors with a critical eye and generally weighing in on related discussions in order to provide a counterbalance to the amount of time and effort that a paid editor can devote to pushing the particular POV they've been paid to represent. --[[User:Aquillion|Aquillion]] ([[User talk:Aquillion|talk]]) 01:31, 5 April 2019 (UTC) *Random thought bubbles - would general sanctions work for some subset of articles prone to paid editing (say the highest risk topics: advertising, marketing and public relations or leveraged financial products targeted at retail investors)? Can we repurpose existing DS regimes to the same effect ([[WP:ARBIPA]], [[WP:ARBCAM]] in particular)? The quality of cryptocurrency articles has improved since [[WP:GS/Crypto]] was put into place, but sometimes I feel tired keeping up with the influx of SPAs. [[User:MER-C|MER-C]] 21:39, 29 March 2019 (UTC) *:{{u|MER-C}} I agree that Crypto has improved since GS. However, I don't know that advertising, marketing, and public relations are the topics most likely to have UPE and so I don't know that we could define this in a way that would make GS possible in this area given the broad scope of topics which potentially have UPE as it encompasses biographies, companies, and products. Best, [[User:Barkeep49|Barkeep49]] ([[User_talk:Barkeep49|talk]]) 04:59, 31 March 2019 (UTC) *::Advertising is high risk because that's what spammers do. If they get the idea that we tolerate them creating articles about themselves and their companies, then it is not a stretch that they think we tolerate them creating articles about their clients. I also forgot we have [[WP:NEWBLPBAN]] for biographies. [[User:MER-C|MER-C]] 09:29, 31 March 2019 (UTC) *It looks like BC1278 removed the notice of this discussion and the notice of the prior conflict of interest noticeboard discussion (archived at {{slink|Wikipedia:Conflict of interest/Noticeboard/Archive 141|Jytdog's efforts against paid editing covered in Media}}) from their [[User talk:BC1278|user talk page]] on March 29. You can see the removal at [[Special:Diff/890053760]]. :Since there has been some interest in the suggestion to restrict paid editors from deleting other editors' comments from their user talk page, I think an RfC to include new guidance at {{slink|Wikipedia:User pages|Removal of comments, notices, and warnings}} ([[WP:BLANKING]]) may be warranted. For the RfC, the proposed addition could be a new bullet point at [[WP:BLANKING]] that states the following {{tq|"important matter"}} may not be removed by the user: :{{tq2|For editors making [[WP:PAID|paid contributions]], any comments and templates (from other editors) related to their edits on a topic in which they have a [[WP:COI|conflict of interest]]. Examples include deletion notices, [[WP:AFC|Articles for Creation]] notices, noticeboard discussion notices, and comments on the editor's paid contributions.}} :Alternatively, here's a stricter option: :{{tq2|For editors making [[WP:PAID|paid contributions]], any comments and templates from other editors, with the exception of obvious [[WP:VD|vandalism]].}} :Would this be helpful, and can this be improved? I'd like to hear your thoughts and suggestions. —&nbsp;'''''[[User:Newslinger|<span style="color:#536267;">Newslinger</span>]]'''&nbsp;<small>[[User talk:Newslinger#top|<span style="color:#708090;">talk</span>]]</small>'' 06:58, 2 April 2019 (UTC) ::I don't think this would help with anything. Feels like we are hunting for solutions to non-existent problems, here. [[User:Jo-Jo Eumerus|Jo-Jo Eumerus]] ([[User talk:Jo-Jo Eumerus|talk]], [[Special:CentralAuth/Jo-Jo Eumerus|contributions]]) 16:37, 2 April 2019 (UTC) ::Agenda editors, who use Wikipedia to smear the subjects of articles (this happens all the time, including from competitors, oppo research firms, disgruntled former employees and foreign governments - it's just not something Wikipedia can easily identity), also like to use User Talk pages to discredit those opposing them. So do overly zealous editors who use User Talk to attack paid editors or their positions, instead of confining their discussions to Article Talk or noticeboards. Two contributors to my User Talk now have indefinite blocks. One of these two verbally attacked me both on User Talk and offline. The HuffPo article's allegations have been discredited. I consider the allegations potentially libelous. Talk served its primary purpose by notifying me of the ongoing discussions. Why should I offer further credence to a discredited article by linking to discussions about it from my own User Talk? [[User:BC1278|BC1278]] ([[User talk:BC1278|talk]]) 16:30, 2 April 2019 (UTC) ::: what I read in the Huffpost article was that there is a paid editor who knows the system of Wikipedia and its rules very well, and sometimes causes disruption in the pursuance of their business goals. What I see here is: :::*this large ANI thread, :::*a huge wall of text at the [[Talk:Caryn_Marooney|Caryn Marooney talk page]], and :::*a new [[Wikipedia:Reliable_sources/Noticeboard#HuffPost_for_paid_editing_at_Axios_(website),_NBC_News,_Caryn_Marooney,_and_other_articles|thread over at Reliable Sources Noticeboard]]. :::That's a lot of gaslighting as far as I am concerned, and to me it confirms the techniques claimed in the HuffPo article. So the HuffPo article is by no means discredited-- unless you also think that the border wall is getting built too.[[User:ThatMontrealIP|ThatMontrealIP]] ([[User talk:ThatMontrealIP|talk]]) 23:53, 2 April 2019 (UTC) :::: Do you know what the word gaslighting means or did you just use it as a synonym for "thing I don't like"? [[Special:Contributions/199.247.43.170|199.247.43.170]] ([[User talk:199.247.43.170|talk]]) 08:49, 3 April 2019 (UTC) ::If the community has reached a point where we no longer want paid editing of any kind and are willing to tolerate the drawbacks of making all paid editing UPE then let's do that. However, I don't think we should be imposing new restrictions of this sweep on declared paid editors. Frankly I would rather come up with some better incentives to motivate people to declare their paid editing. However, I haven't figured out what those incentives might be and acknowledge that what's good for the project might be to just ban all paid editing (though I'm personally not quite there yet). But I am confident that the "middle ground" isn't to stigmatize people following the rules further in ways we don't other editors especially those with strong but unpaid COI. Best, [[User:Barkeep49|Barkeep49]] ([[User_talk:Barkeep49|talk]]) 20:47, 2 April 2019 (UTC) :*I think that this would at least be an improvement. If we're going to allow paid editing at all, it's important to allow editors to know when they're interacting with a paid editor, and to know the general scope and history of that paid editing (ie. understanding that the editor they're trying to convince is unlikely to change their mind on a topic because their paycheck depends on maintaining a particular point of view.) Other notices exist, but preserving talk page discussions would be useful for this purpose. It would also make it harder for a paid editor to conceal a history of [[WP:POV]] or [[WP:TENDENTIOUS]] editing, which is something they're obviously at a higher risk for. Regarding some of the concerns above about other sources of POV existing, or about whether declared paid editors may simply choose to violate the rules and edit covertly if we make things too burdensome for them - this is clearly a risk, but I feel that declared paid editing poses a particular problem for the project's reputation. An editor with a personal POV can still be reasonably convinced; an editor who is being paid to push a particular POV or to make particular edits realistically cannot (at best, they can be convinced that their edits are unlikely to stick, and even then they have incentives to maintain pressure long past the point where anyone else would have compromised or gone elsewhere.) For these reasons, it's important to be harsher with them and to generally make every effort to ensure, as much as possible, that they're refraining from tendentious editing, and to make it harder for them to conceal it if it exists. --[[User:Aquillion|Aquillion]] ([[User talk:Aquillion|talk]]) 01:47, 5 April 2019 (UTC) *This DPE is wasting a lot of community time (cf the list of discussions two comments up), and as far as I can see it is all in pursuance of improving his business. The current model wherein a group of volunteers fulfills the desires of paid editor is, well, fundamentally flawed. I'm certainly not here to do corporate volunteer service. Additionally, I can't see how the quality of the encyclopedia is going to be that much poorer if paid editing is blocked on all counts (with an exception for Wikipedians in residence). For one, with a no paid editing policy, we will know that the primary intention of all editors is to edit with a neutral view and without COI. And in turn, we will know that the encyclopedia is primarily constructed on a non-commercial basis. Just a thought.[[User:ThatMontrealIP|ThatMontrealIP]] ([[User talk:ThatMontrealIP|talk]]) 00:12, 3 April 2019 (UTC) **Well, we wouldn't know ''for certain'', but I broadly agree. I feel like some of the people depending paid editing above don't realize how bad this looks from an external perspective (especially the somewhat befuddling argument that this breaks no rules - I think the article is clear on that; the point is that the fact that it breaks no rules makes the ''entire encyclopedia'' look bad.) It is probably true, as some people have worried, that if we banned all paid editing, people would just do it undisclosed. But I feel that the harm to Wikipedia's reputation from intentionally allowing such paid editing is worse than the damage we'd suffer from people doing it subtly, especially since at the end of the day really controversial stuff goes through talk pages anyway and often comes down to things like knowing the rules and sheer stamina to carry on a protracted dispute - not stuff that our restrictions on paid editing actually do anything to mitigate. Maybe a decade ago, when someone could have swept in and quietly rewritten a medium-profile article with nobody noticing, the danger of undisclosed paid editing was higher and just keeping paid editors off of article-space was helpful. But right now I don't feel it's helping at all. --[[User:Aquillion|Aquillion]] ([[User talk:Aquillion|talk]]) 01:03, 5 April 2019 (UTC) === Prohibition on all paid editing === The comments above show some interest in prohibiting all paid editing (declared or undeclared), with the exception of edits from [[:meta:Wikipedian in Residence|Wikipedians in Residence]] (WiR). The procedure to enact this is described in {{slink|WP:PAID|Changing this policy}}: {{tq2|An alternative policy can revoke the disclosure provision of the terms of use as it applies to the English Wikipedia and replace it with a new policy, which may be stronger or weaker. A proposed alternative policy must be clearly identified in a Request for Comment (RfC) as revoking the WMF policy. Upon approval, the new policy must be listed on the [[:m:Alternative paid contribution disclosure policies|alternative-disclosure policy page]]. The RfC must be conducted in a manner consistent with the [[WP:PROPOSAL|standard consensus-based process for establishing core policies]].}} [[Special:Permalink/529481448|A former disambiguation page for Wikipedia:Paid editing]] lists three failed proposals for paid editing policies and guidelines from 2007 to 2011. Our current policy, [[Wikipedia:Paid-contribution disclosure]], was created in 2015 for consistency with the [[:foundation:Special:Diff/98138|prohibition of undisclosed paid editing in the WMF's terms of use]] in 2014. I found only one previous RfC on paid editing ([[Wikipedia:Requests for comment/Paid editing]]), which took place in 2009 and resulted in no consensus. It has been almost 10 years since that RfC, and many editors have accumulated enough experience dealing with disclosed paid edits to determine whether they are a net positive/negative to Wikipedia. I think it's time to re-evaluate community consensus on whether disclosed paid contributions (excluding [[:meta:Wikipedian in Residence|WiR]]) should continue to be allowed in Wikipedia. What are your thoughts on this? —&nbsp;'''''[[User:Newslinger|<span style="color:#536267;">Newslinger</span>]]'''&nbsp;<small>[[User talk:Newslinger#top|<span style="color:#708090;">talk</span>]]</small>'' 23:25, 6 April 2019 (UTC) * Note the lack of consensus at [[Wikipedia talk:Deletion policy#Undeclared Paid Editor (UPE) product]] that being solely UPE product is even a reason for deletion. : Also, oppose exclusion of Wikipedian in Residence. Why should Wikipedians in Residence not declare? —[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 23:54, 6 April 2019 (UTC) ::To clarify, the proposal continues to allow declared edits from Wikipedians in Residence (i.e. WiR would not be affected by the prohibition). WiR would continue to declare their status. —&nbsp;'''''[[User:Newslinger|<span style="color:#536267;">Newslinger</span>]]'''&nbsp;<small>[[User talk:Newslinger#top|<span style="color:#708090;">talk</span>]]</small>'' 00:10, 7 April 2019 (UTC) ::: I think all paid contributions should be prohibited unless declared. Includes WiR, includes WMF. Simple, no exceptions. I think all COI contributions to mainspace should be prohibited, they must use the talk page, or AfC for new pages. However, undeclared UPE and undeclared COI can only be “suspected”. So how can this prohibition have teeth? —[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 00:37, 7 April 2019 (UTC) :::: Undisclosed paid contributions are already prohibited in [[WP:PAID]] and the [[:foundation:Terms of Use|Terms of Use]]. Despite our current policies, Wikipedia already deals with undisclosed paid editing on an ongoing basis, and this activity is discussed and handled on the [[WP:COIN|conflict of interest noticeboard]]. —&nbsp;'''''[[User:Newslinger|<span style="color:#536267;">Newslinger</span>]]'''&nbsp;<small>[[User talk:Newslinger#top|<span style="color:#708090;">talk</span>]]</small>'' 00:58, 7 April 2019 (UTC) * Disclosed paid contributions? Do we have evidence that honestly declared paid editors have produced such bad product that “prohibition” is required? I think it is an overreaction as likely to succeed as was US Prohibition of alcohol. —[[User:SmokeyJoe|SmokeyJoe]] ([[User talk:SmokeyJoe|talk]]) 00:40, 7 April 2019 (UTC) **Yes, without a doubt. Just peruse the archives at COIN. [[User:John from Idegon|John from Idegon]] ([[User talk:John from Idegon|talk]]) 02:38, 7 April 2019 (UTC) *: Condoning paid contributions (even if disclosed) reflects poorly on Wikipedia's credibility, as it tells readers that Wikipedia's [[WP:NPOV|neutrality]] is up for sale. There is no financial incentive for a company to hire paid editors to make neutral contributions. In fact, it would be irrational (and in [[Public company|publicly traded companies]], a violation of [[fiduciary duty]] to shareholders) for a company to hire paid editors, and then instruct them to not portray the company in as favorable of a light as possible. The interests of most companies are not aligned with Wikipedia's goals to provide readers with neutral, trustworthy content. —&nbsp;'''''[[User:Newslinger|<span style="color:#536267;">Newslinger</span>]]'''&nbsp;<small>[[User talk:Newslinger#top|<span style="color:#708090;">talk</span>]]</small>'' 01:19, 7 April 2019 (UTC) :: == RfC Closure Review (Talk:RuPaul's Drag Race) == I am requesting a review of the closing of [[Talk:RuPaul%27s Drag Race#RfC on names of transgender contestants]] on the grounds that it is not a reasonable summation of the discussion. My attempt to discuss this with the closer can be found [https://en.wikipedia.org/w/index.php?title=User_talk:QEDK&oldid=889055058#Improper_close here]. The RfC takes a look at how the wiki should handle using a transgendered individual's name and is an extention of [[Talk:RuPaul%27s_Drag_Race_(season_2)#RfC|this RfC]]. The close is almost incoherent and makes little sense when you actually read the discussion and take in the view points of those that participated. The last sentence alone makes no sense and is not a representation of the points made in the discussion. The two prevailing viewpoints are 1) to remove ''all'' real names from the articles as they are not included in the credits of the show and are essentially pulled from other sources and 2) to only list the current names of individuals and not the name they used at the time of filming. Most of those supported using their current name supported the idea of removing all the names entirely. Removing real names was not included in the original wording of the RfC but is clearly meant to remove all names from the articles. Removing only the names of transgendered individuals is inappropriate and makes no sense. I request that the community review, revert, and reclose this discussion to represent the consensus that is clearly there. Thanks. [[User:Nihlus|<span style="padding:2px 2px;font-variant:small-caps;color:#000;letter-spacing:-0.5px">'''Nihlus'''</span>]] 04:28, 25 March 2019 (UTC) *I happened upon this shortly after closing and so I read the close and then the RfC and was also puzzled by it also not fully understanding what point {{u|QEDK}} was trying to make with [[MOS:GENDERID]] (which is obviously relevant here). I agree with {{u|Nihlus}} that the consensus, as I see it, is to remove real name from the seasons entirely and to only include their stage names (e.g. how they were referred to on the show). Best, [[User:Barkeep49|Barkeep49]] ([[User_talk:Barkeep49|talk]]) 17:00, 25 March 2019 (UTC) *I don't read this closure as ''prohibiting'' the removal of all non-credited names from the articles in question. I think the closer intended on answering the original RFC question, which related to trans contestants who transitioned after their TV appearance. [[User:Iffy|Iffy]]★[[User Talk:Iffy|Chat]] -- 09:17, 26 March 2019 (UTC) *:I tried to clarify this with the closer and got no where. The consensus that is reached need not necessarily be a direct response or even a level response to the question asked. [[User:Nihlus|<span style="padding:2px 2px;font-variant:small-caps;color:#000;letter-spacing:-0.5px">'''Nihlus'''</span>]] 10:34, 26 March 2019 (UTC) *I would like more participation in this topic before I revert the close myself based on what is here. Thanks. [[User:Nihlus|<span style="padding:2px 2px;font-variant:small-caps;color:#000;letter-spacing:-0.5px">'''Nihlus'''</span>]] 02:48, 30 March 2019 (UTC) * (Uninvolved, did not know about the RfC till now): I don't know where to post this, but I agree with Nihlus both on his !vote and its rationale, and his points here in this thread. Plus it's confusing (for the reader) for a WP article on a TV show not to use credited names. [[User:Softlavender|Softlavender]] ([[User talk:Softlavender|talk]]) 03:38, 30 March 2019 (UTC) *{{u|QEDK}}, I will be altering your close based on the information in this discussion. If you would like to alter it yourself, feel free to do so. [[User:Nihlus|<span style="padding:2px 2px;font-variant:small-caps;color:#000;letter-spacing:-0.5px">'''Nihlus'''</span>]] 22:21, 4 April 2019 (UTC) :: {{re|Nihlus}} Remove my close (not alter) and do as you see fit. --<span style="font-family:'Trebuchet MS',Geneva,sans-serif">[[User:QEDK|<span style="color:#b7e">QEDK</span>]] ([[User talk:QEDK|<span style="color:#fac">後</span>]] ☕ [[Special:Contributions/QEDK|<span style="color:#fac">桜</span>]])</span> 15:04, 5 April 2019 (UTC) :::{{u|QEDK}}, I wasn't asking whether I can alter it. I will alter it/add an addendum that clarifies the close and corrects it to the right consensus. [[User:Nihlus|<span style="padding:2px 2px;font-variant:small-caps;color:#000;letter-spacing:-0.5px">'''Nihlus'''</span>]] 22:35, 5 April 2019 (UTC) ::::{{re|Nihlus}} I don't think you're understanding how it works. You can't alter my close, because then it's not my close anymore. Feel free to add an addendum but an addendum that contradicts my close is against the point. If you're challenging a close, it's for a reversal. And I'm telling you to do as you see fit, as long as you reverse my close first. --<span style="font-family:'Trebuchet MS',Geneva,sans-serif">[[User:QEDK|<span style="color:#b7e">QEDK</span>]] ([[User talk:QEDK|<span style="color:#fac">後</span>]] ☕ [[Special:Contributions/QEDK|<span style="color:#fac">桜</span>]])</span> 06:54, 6 April 2019 (UTC) :::::{{u|QEDK}}, again, I was not asking. [[User:Nihlus|<span style="padding:2px 2px;font-variant:small-caps;color:#000;letter-spacing:-0.5px">'''Nihlus'''</span>]] 13:21, 6 April 2019 (UTC) ::::::{{re|Nihlus}} For someone who doesn't understand how reverting a close works and goes for a reversal on minimal consensus, you seem hell-bent on '''not asking''' or whatever that is supposed to mean. Again, I'm not really doing anything here but telling you that if you intend to alter my close, you '''have''' to reverse the close. Please do not ping me again if your intent is to voice your redundant battleground-y point. --<span style="font-family:'Trebuchet MS',Geneva,sans-serif">[[User:QEDK|<span style="color:#b7e">QEDK</span>]] ([[User talk:QEDK|<span style="color:#fac">後</span>]] ☕ [[Special:Contributions/QEDK|<span style="color:#fac">桜</span>]])</span> 14:00, 6 April 2019 (UTC) :::::::{{u|QEDK}}, do not tell me what I do or don't know. I've been dealing with this mess for long enough due to your poorly thought out and at this point disruptive close. I will fix this situation in the manner I see fit as it falls into policy. As I stated before, I was not asking for your input on how I should proceed. I don't need that information from you nor do I want it. I was merely advising you to alter your close if you saw fit as a courtesy to you. Since you are not taking it, then I will proceed as I originally intended. Thanks. [[User:Nihlus|<span style="padding:2px 2px;font-variant:small-caps;color:#000;letter-spacing:-0.5px">'''Nihlus'''</span>]] 14:32, 6 April 2019 (UTC) ::::::::{{re|Nihlus}} Do me a favour and respect someone's choice when an editor asks you to not ping them. I do not care about your pointless accusations ({{tq|disruptive close}}), I've asked you to do literally what you want and you refuse to let me be. I am not willing to waste my time on your shenanigans, so I recommend you drop the stick. --<span style="font-family:'Trebuchet MS',Geneva,sans-serif">[[User:QEDK|<span style="color:#b7e">QEDK</span>]] ([[User talk:QEDK|<span style="color:#fac">後</span>]] ☕ [[Special:Contributions/QEDK|<span style="color:#fac">桜</span>]])</span> 14:39, 6 April 2019 (UTC) :::::::::My accusation is not pointless, and I have no stick to drop. No one if forcing you to comment here, yet you keep doing it and ''whine'' when I reply. There is no policy in forcing someone to not ping someone as communication is important on a platform such as this. Additionally, pinging me in every reply while asking me to stop is rather odd, as is throwing a subjective condition on your request for me to not ping you. I suggest you just stop contributing to this discussion if you are truly done with it as that is the route I am taking. [[User:Nihlus|<span style="padding:2px 2px;font-variant:small-caps;color:#000;letter-spacing:-0.5px">'''Nihlus'''</span>]] 14:48, 6 April 2019 (UTC) == Regarding massive editing on page [[Pratishtha Sharma]] == hi more than 100 links of sources were deleted of the above article with out any discussion. editor have a view that sources are not of news papers like new york times. contributor has a view point that sources were of Indian popular newspapers as the personality is from India. government sources links were also included under the deleted one. contributor suggested to undo cahnges and a discussion can be done on source before deletion but editor is not ready to repose the same and adamant to his edit. he also quoted a six year old notification which was rectified with out opposition at the time only inside the article. kindly suggest what to do now.[[User:Rusianejohn|Rusianejohn]] ([[User talk:Rusianejohn|talk]]) 13:47, 30 March 2019 (UTC) : I deleted a mass of very weak links provided to support claims for this article. A search on Google reveals few if any reliable sources; many of those deleted were on YouTube. Far from refusing to discuss, I have repeatedly asked Rusianejohn to join the discussion and provide sources, which have not been forthcoming. Today at last Rusianejohn made some preliminary remarks but apart from asking for the weak sources to be reinstated, has provided no evidence of notability. I'm all ears for better sources. [[User:Chiswick Chap|Chiswick Chap]] ([[User talk:Chiswick Chap|talk]]) 15:48, 30 March 2019 (UTC) :: Today only i have checked such mass deletion and i am making a list of new links and important from deleted links supporting the heads. article was not at all based on the you tube links and the remaining 27 links out of 144 is sufficient to support lines but still i will write more links and send to you. you tube links were mostly of episode of regular tv shows on national and international tv channels which supports title- Tv celebrity. most of the big channels has there own you tube channel and they put past episodes on those channel. few put them on there websites. i will mention those links as well. Remaining links deleted by you need to be verify before deletion. i will put them here with new links as well. my only humble request and objection is if we don't understand a language and don't know about one particular source or have confusion, than at least we can talk. talk page is for that only. we can always discuss before execution for old and established articles. this we do at wiki.[[User:Rusianejohn|Rusianejohn]] ([[User talk:Rusianejohn|talk]]) 16:39, 30 March 2019 (UTC) ::: I'm very glad you have started on that. I have been through the sources in the article that I can access: nearly all are dead links or otherwise unusable. I have added quotations to two that are good and usable, but unfortunately those are much too brief to demonstrate notability. If you can find some Hindi sources and translate those we should easily find out whether Sharma is notable, but that isn't a matter for this noticeboard. [[User:Chiswick Chap|Chiswick Chap]] ([[User talk:Chiswick Chap|talk]]) 16:48, 30 March 2019 (UTC) I have send around 20 links with narration for you at talk page (including old and new), you can have a look, during revision of deleted links i have seen links of good news papers which are still active but deleted with the youtube one. those links supported with photo and news of Sharma honored with awards in other country. links of video of press conference on news channels website where she is sitting with minister of other countries, where she was sent by Indian government as culture Ambassador, that press conference is in English and with English narration also. that is the reason why i was dis-satisfied because you have deleted 118 links and it is not possible for any body to check all links so quickly, out of these 118 links you tube links were around 10 only . i can understand that you tube links are not reliable but others are. i can understand that few old links are dead now because of old news and articles that is the reason new news and articles were introduced and were added to the article. earlier also many users contribute to the article and deleted the links but this was my first experience in last 7 years. . [[User:Rusianejohn|Rusianejohn]] ([[User talk:Rusianejohn|talk]]) 06:39, 31 March 2019 (UTC) ::: We are discussing these links on User talk:Rusianejohn, which is sufficient. So far we have agreed that none of the links so far identified are usable for notability, we are examining further links. [[User:Chiswick Chap|Chiswick Chap]] ([[User talk:Chiswick Chap|talk]]) 18:59, 31 March 2019 (UTC) ::The OP has also started this thread [[Wikipedia:Teahouse#Dis-satified with massive edit of sources on article Pratishtha Sharma]]. Some one may want to consolidate them. [[User:MarnetteD|MarnetteD]]&#124;[[User talk:MarnetteD|Talk]] 06:50, 31 March 2019 (UTC) :::: after the last 2 uploads of links and discussion i have uploaded more links of government sites in English as well Hindi news links with google translations as [[User:Chiswick Chap|Chiswick Chap]] unable to translate. as far as previous links i had a view point that those links support the content and from reputed sites and can be easily translated but still i have uploaded more and more links, this time in English as well Hindi with translation supporting the content completely.[[User:Rusianejohn|Rusianejohn]] ([[User talk:Rusianejohn|talk]]) 09:51, 1 April 2019 (UTC) ::: we are working on it cooperatively, discussed and listed links which can be used. [[User:Rusianejohn|Rusianejohn]] ([[User talk:Rusianejohn|talk]]) 06:11, 2 April 2019 (UTC) == Possible NLT at BLPN == Would an admin mind taking a look at [[:WP:BLPN#Wang Zheng (pilot)]]? {{u|CTF99}} is claiming to be an attorney for [[:Wang Zheng (pilot)]] and is expressing some concerns about the article. That's probably OK except for the last paragraph of their post where the seem to move into NLT territory. There may also be some undisclosed PAID editing as well since the account has been editing the article over the years, but hasn't declared any connection. -- [[User:Marchjuly|Marchjuly]] ([[User talk:Marchjuly|talk]]) 14:15, 30 March 2019 (UTC) :Given the exceptional length of the post, the potential NLT bit is: {{tq|I am happy to work with you to resolve this but be advised that if the matter is not resolved expeditiously, and any libelous, contentious, or conflict of interest material is not removed, Ms. Wang will proceed to exercise all available remedies and hold accountable all responsible individuals for all damages permitted by law, including attorneys' fees}}. I'll leave it to others to work this out. [[User:Mr rnddude|Mr rnddude]] ([[User talk:Mr rnddude|talk]]) 14:20, 30 March 2019 (UTC) :Appears to have [https://en.wikipedia.org/w/index.php?title=Wikipedia:Biographies_of_living_persons/Noticeboard&diff=prev&oldid=890162865&diffmode=source been struck]. [[User:SQL|<span style="font-size:7pt;color: #fff;background:#900;border:2px solid #999">SQL</span>]][[User talk:SQL|<sup style="font-size: 5pt;color:#999">Query me!</sup>]] 16:49, 30 March 2019 (UTC) ::The content which might be seen as violation of NLT has been stricken as pointed out by [[:WP:SQL]]; the undisclosed COI and paid editing concerns, however, have still not been clarified. CTF99 not only has identified themselves as being an attorney representing Wang, but specifically someone named James Fretcher. This could be the same Jim Fletcher attributed in [https://www.sun-sentinel.com/local/palm-beach/fl-palm-chinese-world-flight-20160729-story.html this 2016 article] about Wang, and also the same "Jim Fretcher" mentioned as being a "China General Aviation LLC" manager in [https://www.sgvtribune.com/2018/07/24/pilot-sues-when-she-doesnt-get-award-money-for-being-first-asian-woman-to-fly-around-globe/ this 2018 article] about a lawsuit Wang has filed. So, CCLT should declare their connection to Wang per [[:WP:DECLARECOI]] and [[:WP:PAID]] and clarify that he is who he's claiming to be and not a [[:WP:IMPERSONATION]]. -- [[User:Marchjuly|Marchjuly]] ([[User talk:Marchjuly|talk]]) 23:09, 30 March 2019 (UTC) :::Or maybe the James Frechter who is her lawyer in [https://www.courthousenews.com/chinese-aviatrix-launches-dogfight-in-ny-court this] article or the Jim Frechter who is her husband in [https://www.sun-sentinel.com/south-florida-parenting/fl-flight-around-world-completed-20160922-story.html this one]. See also the [[Wikipedia:Conflict_of_interest/Noticeboard#Wang_Zheng_(pilot)|COIN thread]] that started it all.[[User:Hydromania|Hydromania]] ([[User talk:Hydromania|talk]]) 01:43, 31 March 2019 (UTC) :::: There is also the issue of multiple accounts/IP addresses being used by what appears to be the same editor, in particular {{u|CTF99}} and {{u|Kigenkigen}}. [[User:Melcous|Melcous]] ([[User talk:Melcous|talk]]) 03:07, 31 March 2019 (UTC) :::::Blocks handed out pursuant to [[Wikipedia:Sockpuppet investigations/Kigenkigen]]. [[User:Yunshui|Yunshui]]&nbsp;[[User talk:Yunshui|<sup style="font-size:90%">雲</sup>]][[Special:Contributions/Yunshui|<sub style="font-size:90%">水</sub>]] 09:08, 1 April 2019 (UTC) ===Outing?=== I'm going to add {{u|EdiK2016}} to this discussion. This is another [[:WP:SPA]] which appears to be involved somehow in the off-Wikipedia dispute with Wang mentioned in the article; however, the reason I'm adding them to this discussion is that posts made at [[:Talk:Wang Zheng (pilot)]] (which have since been removed, but are still in the page's history) by the account seem really close to if actually not succeeding at [[:WP:OUTING]] of Kigenkigen. -- [[User:Marchjuly|Marchjuly]] ([[User talk:Marchjuly|talk]]) 21:03, 31 March 2019 (UTC) :Other suspected socks of EdiK2016, {{u|Holmes767}}, {{u|Douwang}}, {{u|Douwang1124}}. - <span style="font-family:Trebuchet MS">[[User:FlightTime|<span style="color:#800000">'''FlightTime'''</span>]] <small>([[User talk:FlightTime|<span style="color:#FFD700">'''open channel'''</span>]])</small></span> 21:10, 31 March 2019 (UTC) ::Correct me if I'm wrong, but those are more likely to be socks of either Kigenkigen or CTF99. I believe they want the removal of information critical of the subject putting them against EdiK2016 who wants it included. [[User:Hydromania|Hydromania]] ([[User talk:Hydromania|talk]]) 22:51, 31 March 2019 (UTC) :::EdiK2016 is not (at least from a checkuser standpoint}} related to any of the Kigenkigen socks. [[User:Yunshui|Yunshui]]&nbsp;[[User talk:Yunshui|<sup style="font-size:90%">雲</sup>]][[Special:Contributions/Yunshui|<sub style="font-size:90%">水</sub>]] 09:09, 1 April 2019 (UTC) == Answers in Genesis == {{atop | status = No action | result = This is a content dispute and nothing more. Please seek assistance via one of the methods listed at [[WP:DR]]. [[User:John from Idegon|John from Idegon]] ([[User talk:John from Idegon|talk]]) 00:09, 31 March 2019 (UTC) {{nac}} Add: requests for closure go at [[WP:AN]]. There's a section there just for that. [[User:John from Idegon|John from Idegon]] ([[User talk:John from Idegon|talk]]) 00:14, 31 March 2019 (UTC) }} Can an uninvolved administrator conclude on [[Talk:Answers in Genesis#And (thus) rejects vs (thus) rejecting|this discussion]]? Here is my take on it: there are currently 2 'support' !votes (me and [[user:1990'sguy]]) and 3 'oppose' !votes. However, all three 'oppose' voters have either not stated any issue with my proposal, instead arguing that it is not an ''improvement'' over the current version (like [[user:Doug Weller]] and [[user:Nick Thorne]]) or have explicitly stated that they are 'OK' with the proposed version (like [[user:Guy Macon]]). As per [[WP:TALKDONTREVERT]], which says that "consensus can be assumed if no editors object to a change", that should imply that there is a consensus to implement the proposal. The only ''objection'' made against the proposal is by [[user:Rhododendrites]], but these issues appear to me to be easily fixable ([https://en.wikipedia.org/w/index.php?title=Talk:Answers_in_Genesis&diff=890105567&oldid=890100831]), and the user hasn't explicitly stated that they oppose the general proposal. Either way, even if we count their comment as an 'oppose' !vote, we still have a 2 against 1 in favour of the proposal. Feel free to add your take on the situation under this post.[[User:Oldstone James|<span style="color:white;background:#21E907"><sup>O</sup></span><span style="color:grey;background:#21E907">l</span><span style="color:#fff;background:#21E907"><sup>J</sup></span><span style="color:grey;background:#21E907">a</span>]] 21:49, 30 March 2019 (UTC) :'''I object to the change.''' At the same time I am OK with the change if that is the way the consensus goes. I don't always get what I want and the proposed version isn't awful; it just isn't quite as good as what is there now. :Re "the only ''objection'' made against the proposal is by Rhododendrites", I object to Oldstone James' attempt to decide which objections are real and which don't meet his standard for "real" objections. Everyone who !voted "oppose" objects to the change, whether or not Oldstone James is willing to accept the fact of those objections. :I would also note that I asked for a clarification from the protecting admin on the article talk page.[https://en.wikipedia.org/w/index.php?title=Talk%3AAnswers_in_Genesis&type=revision&diff=890107893&oldid=890105567] He is probably off enjoying himself on the Wikipedia Administrator's Yacht weekly cruise to the Wikipedia Administrator's Private Island and hasn't edited since I asked. I would welcome any administrator putting down his [[Dom Pérignon]] for a moment and answering my question. --[[User:Guy Macon|Guy Macon]] ([[User talk:Guy Macon|talk]]) 23:35, 30 March 2019 (UTC) {{abot}} ===Behavioral issues=== Let's reopen this for another purpose, please. I respect {{u|Oldstone James}}' tenacity and that he keeps his cool in a situation that is no doubt extremely frustrating for him. However, can we get an uninvolved admin to review the behavioral issues going on at [[Answers in Genesis]]? *It seems like no change made by James isn't followed by a brief edit war, going back a couple years, though typically stopping short of 3RR. *I have mixed feelings about the copious amount of text on the talk page. In part I can empathize with repeatedly trying to be understood or come to an understanding, but now that it's sprawling to multiple noticeboards, too (AN3, ANI, DRN), it's hard not to see this as disruptive given the way the discussion has gone so far. *I was furthermore concerned about canvassing. There was [https://en.wikipedia.org/w/index.php?title=User_talk:1990%27sguy&diff=prev&oldid=889759869 this seemingly blatant example] that resulted in [https://en.wikipedia.org/w/index.php?title=Talk:Answers_in_Genesis&diff=889772587&oldid=889767851 predictable support] shortly thereafter. I didn't bother mentioning it at the time, because, to be fair, 1990'sguy had participated on the page and would likely have supported this anyway, but it's not a good look. It was furthermore followed a little while later by [https://en.wikipedia.org/w/index.php?title=User_talk:LittlePuppers&diff=prev&oldid=889887190 three] [https://en.wikipedia.org/w/index.php?title=User_talk:AKA_Casey_Rollins&diff=prev&oldid=889887971 YGM] [https://en.wikipedia.org/w/index.php?title=User_talk:JasperTech&diff=prev&oldid=889888517 notifications] to three people who just happen to be editors that have taken issue in the past with the way AiG is characterized along the lines of e.g. pseudoscience. Unlike 1990'sguy, these are not people who were already involved in the current discussions. Again, to be fair, we don't know the content of those messages, and the recipients didn't participate in the discussion, but again, it's not a good look. I'm not proposing anything in particular -- just suggesting an uninvolved admin take a look, for the sake of all the time that's being expended over the last few days (and potentially much more, now that it's at DRN, too). &mdash; <samp>[[User:Rhododendrites|<span style="font-size:90%;letter-spacing:1px;text-shadow:0px -1px 0px Indigo;">Rhododendrites</span>]] <sup style="font-size:80%;">[[User_talk:Rhododendrites|talk]]</sup></samp> \\ 03:22, 31 March 2019 (UTC) :What you mean by edit war is actually an attempt to find [[WP:EDITCONSENSUS|consensus through editing]], as I change my edit every time. I did not know about [[WP:BLUDGEON]], hence the large amount of text. I have since reduced the amount of text I post. As for multiple noticeboards, I have clearly only tried DRN after being told so in ANI. As for canvassing, I only tried to notify users who have previously proposed changes that are similar to mine. However, since you mentioned behavioural issues, it would be unfair to not also look into [[User:Roxy the dog]], who has launched several [[WP:PERSONAL]] attacks at me, including telling me to "fuck off" and admitting that is a personal attack ([https://en.wikipedia.org/w/index.php?title=Talk:Answers_in_Genesis&diff=889374025&oldid=889356043]), implying that I can't count ([https://en.wikipedia.org/w/index.php?title=User_talk:Doug_Weller&diff=890040948&oldid=890040221]), implying that I am blind, and others; as well as [[user:Guy Macon]] who has launched 1/2 personal attacks (implying that I am an unreasonable person and ungrounded accusion of me not abiding by consensus) in [https://en.wikipedia.org/w/index.php?title=Wikipedia:Dispute_resolution_noticeboard&diff=890242150&oldid=890237866 this post].[[User:Oldstone James|<span style="color:white;background:#21E907"><sup>O</sup></span><span style="color:grey;background:#21E907">l</span><span style="color:#fff;background:#21E907"><sup>J</sup></span><span style="color:grey;background:#21E907">a</span>]] 03:46, 31 March 2019 (UTC) ::Oldstone James has been blocked for a week by [[User:Black Kite]]. [[User:Doug Weller|<span style="color:#070">Doug Weller</span>]] [[User talk:Doug Weller|talk]] 19:44, 31 March 2019 (UTC) :::On 31 March, after the previous protection expired and the edit warring immediately resumed (without Oldstone James who is still blocked), the page was protected for a week. That protection expires tomorrow. If the edit war starts up again, perhaps we should consider blocking individual edit warriors. --[[User:Guy Macon|Guy Macon]] ([[User talk:Guy Macon|talk]]) 12:41, 6 April 2019 (UTC) == Suspicious activities to promote [[Jordan Harbinger]] == [[User:BodegaBiscuit]] created an account on 9 Feb to add content and information on [[Jordan Harbinger]] page. There after he strategically nominating AFD other podcasters pages and started voting from the IP addresses, [[Wikipedia:Articles_for_deletion/Lewis_Howes|first example]], [[Wikipedia:Articles_for_deletion/John_Lee_Dumas|another example]], [https://en.wikipedia.org/w/index.php?title=Wikipedia:Articles_for_deletion/Ramit_Sethi&oldid=890339464 another one here]. Users edits need to be reversed and blocked from Wiki for Cohort practices to promote their client [[Jordan Harbinger]] on Wiki. [[Special:Contributions/157.37.252.223|157.37.252.223]] ([[User talk:157.37.252.223|talk]]) 19:59, 2 April 2019 (UTC) Do you have specific evidence of actual disruptive editing? Are there any problematic additions to the Jordan Harbinger page? Has there been any checkuser done to confirm possible votestacking? [[User:David Fuchs|<span style="color: #cc6600;">Der Wohltemperierte Fuchs</span>]]<sup><small>[[User talk:David Fuchs|<span style="color: #ff6600;">(talk)</span>]]</small></sup> 20:58, 2 April 2019 (UTC) :I don't know about all the other stuff, but the article on Harbinger bears many of the typical earmarks of UPE. [[User:John from Idegon|John from Idegon]] ([[User talk:John from Idegon|talk]]) 21:17, 2 April 2019 (UTC) :{{re|David Fuchs}} There has been a troll stalking, harassing {{u|Bonadea}} with newly created accounts such as [[User talk:B4onadea]], not sure if it's related, but the username is similar. - <span style="font-family:Trebuchet MS">[[User:FlightTime Phone|<span style="color:#0047AB">'''FlightTime Phone'''</span>]] <small>([[User talk:FlightTime Phone|<span style="color:#0047AB">'''open channel'''</span>]])</small></span> 21:11, 2 April 2019 (UTC) ::Definitely not related to that LTA user. The pattern is nothing like that. [[User:RickinBaltimore|RickinBaltimore]] ([[User talk:RickinBaltimore|talk]]) 22:34, 2 April 2019 (UTC) == Unban request by [[User:Cuatro Remos]] aka [[User:Diego Grez-Cañete]] == {{archive top|status=withdrawn|result=Cuatro Remos has [https://en.wikipedia.org/w/index.php?title=User_talk%3ACuatro_Remos&type=revision&diff=890770746&oldid=890684158 withdrawn] this request. [[User:Huon|Huon]] ([[User talk:Huon|talk]]) 15:22, 3 April 2019 (UTC)}} {{user|Cuatro Remos}} posted the following unban request on their [[User talk:Cuatro Remos|talk page]] (I assume he lost the password for {{user|Diego Grez-Cañete}}.) I'm copying it here for a community discussion. [https://en.wikipedia.org/w/index.php?title=Wikipedia:Administrators%27_noticeboard/Incidents&diff=692499699&oldid=692496853#Diego_Grez-Ca%C3%B1ete_(yes,_again) Relevant ban discussion]. [[User:Huon|Huon]] ([[User talk:Huon|talk]]) 21:01, 2 April 2019 (UTC) :It's been half a year since I last requested to be unblocked. It's been a lengthy ban that has made me rethink about my past behavior and I'm convinced these actions were not happy, and will not repeat them in future. I registered here in 2006, thirteen years ago, and it seems like it was yesterday, but time passes and people grow old. I made a lot of stupid mistakes, we all do, it's part of growing. It would be foolish to repeat them all over again, and you can be sure I won't. I would like to return to the encyclopedia, most likely not for active contributing, but for the usual minor corrections, referencing and the like, something I enjoy doing. Also, in the meantime, I have been actively contributing on the Spanish Wikipedia, creating thousands of articles (like I once did here), making a positive contribution. By the way, I have not used socks nor anything. Hopefully you will let me back, after so long. [[WP:SO|After all, bans are not meant to last forever, are they?]] :PS. During my time here, I contributed over 50 DYKs (see [[Wikipedia:List_of_Wikipedians_by_number_of_DYKs|this]], find me as Küñall, my former username), several good articles and tons of new articles. *'''Nope''' - I'd just finished writing out a conditional unban (based on someone checking his claims) when a check on his spanish wikipedia activities realised that he received a week-long block...just 15 days ago. [https://es.wikipedia.org/w/index.php?title=Especial:Registro&page=User%3ACuatro_Remos&type=block Spanish Block Log]. A week-long block as a functionally new block (5 years after your last block on that wiki) indicates something significant. I ''would'' appreciate it if someone can look at those diffs (my computer won't load them), just so we can see what is what. [[User:Nosebagbear|Nosebagbear]] ([[User talk:Nosebagbear|talk]]) 21:40, 2 April 2019 (UTC) **I don't know if I can comment here since I'm not an admin. But as I can see in the diffs, the user was involved in an editing war regarding the political positions of [[José Antonio Kast]] and the user was blocked for one week for using bad and defamatory language breaking the es.wiki etiquette rules. (If I'm not allowed to comment here, please revert my edition)--[[User:SirEdimon|SirEdimon]] ([[User talk:SirEdimon|talk]]) 21:54, 2 April 2019 (UTC) *'''User's Response''' (Copied over from Talk Page) - "{{Ping|Nosebagbear}} I would appreciate if you could copy this over to the noticeboard. Indeed, as you note, I was blocked not long ago at the Spanish Wikipedia, as [[User:SirEdimon|SirEdimon]] says, over a dispute on the [[José Antonio Kast]] article. I disagreed with one of the users, who is their supporter, for pushing a version of the article which is very favorable to the politician, a very controversial one in Chile, specifically for being a far-right politician, using several references to support this fact. As I saw their proposal, I used a mean word, related to sex (intended to mean it was self-pleasing) to describe their changes. I did not contest the block, mainly because it was really short, and because I also agreed it was not a great word choice to describe my opinion. Anyway, I apologized to the user in question. During my block, however, they kept pushing some changes favorable to Kast. [[:es:Discusión:José Antonio Kast|You can have a look at the article talk]]: several other users have agreed that this user (Juan Villalobos) took advantage of my block to keep on pushing these POV-ish changes, and there is (some) consensus that Villalobos' edits should be reverted. Explaining this, I want to mean that I was blocked just for an unhappy word choice, not particularly for the editorial dispute. I hope this can clarify this situation." ::{{Ping|SirEdimon}} - Yep, you (and I!) are absolutely entitled to edit on the general AN board. [[User:Nosebagbear|Nosebagbear]] ([[User talk:Nosebagbear|talk]]) 22:05, 2 April 2019 (UTC) :::Thank you for clarifying. I didn't know if I could, but as I can read some Spanish I thought my contribution would be helpful in this case--[[User:SirEdimon|SirEdimon]] ([[User talk:SirEdimon|talk]]) 22:17, 2 April 2019 (UTC) ::::Yes indeed, that kind of help is valuable, thank you. [[User:Boing! said Zebedee|Boing! said Zebedee]] ([[User talk:Boing! said Zebedee|talk]]) 08:46, 3 April 2019 (UTC) *'''No''' How is the behavior for the block just given on Spanish wiki any different than the behavior that resulted in the English wiki ban? It seems like the edits that just got this user blocked over at es.wiki are ''exactly'' the same sort of edits that resulted in the ban here, and the explanation for the block that was given doesn't mitigate that fact in any way. [[User:Grandpallama|Grandpallama]] ([[User talk:Grandpallama|talk]]) 10:10, 3 April 2019 (UTC) {{archive bottom}} == User:94.118.5.226 == They have some threating stuff to Delta Quad on their talk page. Please do something about it <!-- Template:Unsigned IP --><small class="autosigned">—&nbsp;Preceding [[Wikipedia:Signatures|unsigned]] comment added by [[Special:Contributions/85.118.19.165|85.118.19.165]] ([[User talk:85.118.19.165#top|talk]]) 09:32, 3 April 2019 (UTC)</small> <!--Autosigned by SineBot--> :I've dealt with it, thanks. [[User:Boing! said Zebedee|Boing! said Zebedee]] ([[User talk:Boing! said Zebedee|talk]]) 09:36, 3 April 2019 (UTC) == [[:S. Kalyanasundaram]] == Can some admin take a look at this and figure out the best way to deal with it? The [[:Special:diff/Kailash29792/890890720|intent]] of the creator {{u|Kailash29792}} might have been good, but bad articles shouldn't really be created as way to stop [[:WP:DE|disruption]] on another Wikipedia page. The problem was that {{u|Ntkkalyanasundaram}} kept trying to create this article at [[:Kalyanasundaram]] (a [[:WP:DAB|diambiguation page]]). This was wrong, but it was likely a mistake being made by a new editor not familiar with Wikipedia. At best the "S.Kalyanasundaram" might be OK to [[:WP:USERFY]] or even [[:WP:DRAFTIFY]] if Ntkkalyanasundaram wants to continue working on it, but it's doesn't belong in the article namespace and it shouldn't have been put there. -- [[User:Marchjuly|Marchjuly]] ([[User talk:Marchjuly|talk]]) 08:01, 4 April 2019 (UTC) :Someone may protect the DAB page since Ntkkalyanasundaram doesn't appear to respond to talk page messages, and therefore may continue his disruptive editing. --<span style="white-space:nowrap;font-family: Papyrus">[[User:Kailash29792|<b style="color: black;">Kailash29792</b>]] [[User talk:Kailash29792|<span style="color: black;">(talk)</span>]] </span> 08:10, 4 April 2019 (UTC) ::[[:WP:RPP]] would've been and is still an option, but there was really no reason for you to create an article that you knew was going to quite quickly deleted to try and indirectly "protect" the dab page. -- [[User:Marchjuly|Marchjuly]] ([[User talk:Marchjuly|talk]]) 08:18, 4 April 2019 (UTC) :::Semi-protected the DAB and G11ed the "article", which was essentially a campaign webpage. Ditto for the userpage. Have warned {{u|Ntkkalyanasundaram}} about writing autobiographies; a [[WP:NOTHERE]] block would be justified if they continue. {{u|Kailash29792}}'s creation of the article was non-ideal, but would cut them slack for action born out of understandable frustration. Let me know if I missed something. [[User:Abecedare|Abecedare]] ([[User talk:Abecedare|talk]]) 08:21, 4 April 2019 (UTC) ::::Actually, I thought he would properly develop the article if it existed, that is why I created it. I should have known better, so I apologise. --<span style="white-space:nowrap;font-family: Papyrus">[[User:Kailash29792|<b style="color: black;">Kailash29792</b>]] [[User talk:Kailash29792|<span style="color: black;">(talk)</span>]] </span> 08:27, 4 April 2019 (UTC) == [[Dendrobium]] == Through a series of page moves, most of the historical edits to [[Dendrobium]] seem to have ended up at [[Draft:Dendrobium]]. Can an admin please have a look and merge the page histories as needed? Thank you. [[User:Deli nk|Deli nk]] ([[User talk:Deli nk|talk]]) 20:16, 4 April 2019 (UTC) :There's also [[Draft:Dockrillia]] and {{-r|Vappodes}}. I'm headed out the door right now, but this is rather odd... [[User:Primefac|Primefac]] ([[User talk:Primefac|talk]]) 20:35, 4 April 2019 (UTC) ::Update: also {{-r|Epigeneium}} and {{-r|Dockrillia}}. There's discussion at [[Talk:Dendrobium#Drafts]], and I'll look into what is going on. [[User:Primefac|Primefac]] ([[User talk:Primefac|talk]]) 21:43, 4 April 2019 (UTC) :::Okay, so I've reversed all of the page moves and dealt with attribution and copy/paste issues. Out of curiosity, is it worth deleting the ridiculous number of moves out of the page history? {{-r|Vappodes}} isn't too bad but the history of [[Dendrobium]] is rather frightening. [[User:Primefac|Primefac]] ([[User talk:Primefac|talk]]) 03:45, 5 April 2019 (UTC) == Motion: India-Pakistan == The Arbitration Committee has resolved by [[Special:Permalink/890994939#Motion:_India-Pakistan|motion]] that: {{ivmbox|1={{u|SheriffIsInTown}}'s [https://en.wikipedia.org/w/index.php?title=User_talk%3ASheriffIsInTown&type=revision&diff=841345083&oldid=841015004 topic ban] from pages related to conflict between India and Pakistan is lifted, subject to a probationary period lasting six months from the date this motion is enacted. During this period, any uninvolved administrator may re-impose the topic ban as an arbitration enforcement action, subject to appeal only to the Arbitration Committee. If the probationary period elapses without incident, the topic ban is to be considered permanently lifted.}} For the Arbitration Committee, -- [[User talk:DeltaQuad|<span style="color:white;background-color:#8A2DB8"><b>Amanda</b></span>]] <small>[[User:DeltaQuad|(aka DQ)]]</small> 23:04, 4 April 2019 (UTC) : Discussion at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Motion:_India-Pakistan]]''' == Cant edit on en-wiki == {{moved discussion from|Wikipedia:Administrators' noticeboard/Non-autoconfirmed posts}} --[[User:DannyS712|DannyS712]] ([[User talk:DannyS712|talk]]) 01:08, 5 April 2019 (UTC) Hello, I daily use Tor and en-wiki is the only one that I can't edit. I have a global IP block exemption but this wiki request local IPBE which was granted then removed. Tor allow me to add level of privacy and security on my public wifi connection. I currently an OTRS member and check user (CU) on french Wikipedia. So I think I dont have to demonstrate that I can't represent any danger for the projet. I'm in this ridiculous situation that I can't correct errors on the wiki without spam the button "change IP" in order to find an IP which is not blocked yet. Because I'd like to edit en-wiki like any others wiki without have the need to do blocage bypass or level down the level of protection of my internet connection, I request again the local IPBE statut. Regards. --[[User:Gratus|Gratus]] ([[User talk:Gratus|talk]]) 22:52, 4 April 2019 (UTC) {{cot}} :{{ping|Gratus}} would you like me to post this on the main administrators' noticeboard? Alternatively, I suggest reading [[Wikipedia:IP block exemption#Requesting and granting exemption]], which explains how to get the right. Adding it to such a high profile location may not be advisable. --[[User:DannyS712|DannyS712]] ([[User talk:DannyS712|talk]]) 23:49, 4 April 2019 (UTC) ::{{ping|DannyS712}} Yes it would be great. Since I had aready do all the Unblock Ticket Request System once and have already my identity verified by the WMF, I prefer to not lost my time (and administrators's time) if it's to get the right and see it removed again, so I think directly launch the discussion is better. But if you know a page more related for "unusual" request than noticeboard, feel free to copy at the right place. Regards. --[[User:Gratus|Gratus]] ([[User talk:Gratus|talk]]) 00:40, 5 April 2019 (UTC) :::{{ping|Gratus}} Well you previously had the right, but it was removed because apparently you don't meet the requirements (see the {{logid|72631057|user rights log entry}}), but if you wan't I'll move it. --[[User:DannyS712|DannyS712]] ([[User talk:DannyS712|talk]]) 00:52, 5 April 2019 (UTC) ::::{{ping|DannyS712}} When the right was removed (the requirements was the same that when it was given), I was on (little) safer Wifi connection and, it's the great difference, I wasn't the owner of tools as critical as CU and could take more risks like simply use https connection in public wifi to login.--[[User:Gratus|Gratus]] ([[User talk:Gratus|talk]]) 01:04, 5 April 2019 (UTC) {{cob}} :{{reply to|Gratus}} moved --[[User:DannyS712|DannyS712]] ([[User talk:DannyS712|talk]]) 01:08, 5 April 2019 (UTC) *I seem to recall something from around that time wher one or two admins were going around and revoking IPBE from lots of accounts. The admin who did so in this case left Wikipedia in 2016. I'm inclined to just grant it again as this is obviously a user in good standing even if they have relatively few edits on this particular project. [[User:Beeblebrox|Beeblebrox]] ([[User talk:Beeblebrox|talk]]) 01:20, 5 April 2019 (UTC) *:{{done}}. [[User:Beeblebrox|Beeblebrox]] ([[User talk:Beeblebrox|talk]]) 01:23, 5 April 2019 (UTC) == SPI vandal active... == {{atop|Indeffed by [[User:Galobtter|Galobtter]]--[[User:Ymblanter|Ymblanter]] ([[User talk:Ymblanter|talk]]) 12:21, 5 April 2019 (UTC)}} {{user|Star403}}. -- [[User:Cabayi|Cabayi]] ([[User talk:Cabayi|talk]]) 09:26, 5 April 2019 (UTC) {{abot}} ==Unblock request== Hey admins--can one of you please have a look at an unblock request that's been open for a while? It's on [[User talk:YOUSAFVENNALA]]. [[User:Drmies|Drmies]] ([[User talk:Drmies|talk]]) 17:24, 5 April 2019 (UTC) :{{done}} and declined. This is their 5th (I think) unblock request. I told them about WP:SO but this is getting disruptive. If they post anymore unblock requests between now and September I would turn off their TPA. -[[User:Ad Orientem|Ad Orientem]] ([[User talk:Ad Orientem|talk]]) 17:54, 5 April 2019 (UTC) ==Wanted to create a page for Anthology Resource Vol. 1: △△== One of the subsections on [[Music of Twin Peaks]], [[Music of Twin Peaks#Anthology Resource Vol. 1: △△|Anthology Resource Vol. 1: △△]], has enough citations to warrant its own page, [[Anthology Resource Vol. 1: △△]]. However, when I tried creating the page, it said the page I was trying to create has been restricted to administrators at this time. Likely because of the "△△" at the end of the title. However, that is the name of the album. Can this be un-restricted? [[User:Flowerkiller1692|Flowerkiller1692]] ([[User talk:Flowerkiller1692|talk]]) 20:24, 5 April 2019 (UTC) :It can be an admin, template editor, or page mover. However, I do not think it does "have enough citations to warrant its own page". --[[User:Izno|Izno]] ([[User talk:Izno|talk]]) 21:02, 5 April 2019 (UTC) ::Created at [[Anthology Resource Vol. 1: △△]], currently as a redirect to [[Music of Twin Peaks]], since the redirect is harmless if it turns out there isn't enough to warrant a standalone article. Once you write the article, make sure you also create [[Anthology Resource Vol. 1]] as a redirect to it as realistically nobody searching for it is going to type the triangles.&nbsp;&#8209;&nbsp;[[User:Iridescent|Iridescent]] 21:04, 5 April 2019 (UTC) == [[User:Thegooduser1]] == {{atop | status = | result = Blocked. [[User:SQL|<span style="font-size:7pt;color: #fff;background:#900;border:2px solid #999">SQL</span>]][[User talk:SQL|<sup style="font-size: 5pt;color:#999">Query me!</sup>]] 22:31, 5 April 2019 (UTC) }} this account ain't me. --[[User:Thegooduser|<span style="color: teal">'''Thegooduser'''</span>]] [[User talk:Thegooduser|<span style="color: maroon">'''Life Begins With a Smile :)'''</span>]] <span style="color:red">🍁</span> 21:40, 5 April 2019 (UTC) {{abot}} == [[:User talk:Vlad Sandulescu]] == {{lu|Vlad Sandulescu}} All of this editor's contributions seem to be to their user page and user talk page. Many new editors similarly focus on their user space and it's hoped that they will eventually move on to editing articles, etc. This particular editor, however, seems to be moving into [[:WP:NOTWEBHOST]] territory particularly with respect to their user talk page. The revisions made to the Teahouse welcome template added by {{u|HostBot}} and the self awarding of various barnstars seems strange. Some of the content is not in English so I'm not sure what it says, but the stuff in English seems to be [[:WP:UPNO]] stuff which shouldn't be on their user page yet alone his user talk page. So, I'm wondering if a few admins could look at this and see if something needs to be done. -- [[User:Marchjuly|Marchjuly]] ([[User talk:Marchjuly|talk]]) 00:33, 6 April 2019 (UTC) :Most of the text is in Romanian and could be construed as antisemitic. [[User:Tgeorgescu|Tgeorgescu]] ([[User talk:Tgeorgescu|talk]]) 06:38, 6 April 2019 (UTC) :Deleted from his talk page at {{diff2|891300306}}, reason: [[WP:NOTWEBHOST]]. [[User:Tgeorgescu|Tgeorgescu]] ([[User talk:Tgeorgescu|talk]]) 01:28, 7 April 2019 (UTC) ::*Sandbox talk page deleted as copyright violation of http://basarabialiterara.com.md/?p=22135 and per [[WP:U5]] ::*User talk page revisions deleted as copyright violation of http://www.ziaristionline.ro/2011/02/01/de-ce-a-fost-mazilit-caragiale-din-piata-universitatii-addenda-justificarea-unor-expulzari/ ::*User blocked https://en.wikipedia.org/w/index.php?title=Special:Log&page=User%3AVlad%20Sandulescu&type=block ::Suggesting closure; any appeal needs to be made on [[User talk:Vlad Sandulescu]] anyway. [[User:ToBeFree|~ ToBeFree]] ([[User talk:ToBeFree|talk]]) 02:54, 7 April 2019 (UTC) == User ‘Snowflake91’ deleted information == user just deleted Momoland’s (South Korean Girl Group) music show wins from their page. <!-- Template:Unsigned IP --><small class="autosigned">—&nbsp;Preceding [[Wikipedia:Signatures|unsigned]] comment added by [[Special:Contributions/76.174.116.0|76.174.116.0]] ([[User talk:76.174.116.0#top|talk]]) 03:14, 6 April 2019 (UTC)</small> <!--Autosigned by SineBot--> *{{user|Snowflake91}} wasn't notified about this report, I've notified him now. This appears to have evolved into some moderate puppet-farming, see for example [https://en.wikipedia.org/w/index.php?title=Talk:Momoland&action=history Talk:Momoland] or [https://en.wikipedia.org/w/index.php?title=User_talk:Chubbybangs&action=history User talk:Chubbybangs] for some of the actors. In any event this doesn't belong on [[WP:AN]]. (And if it did, should be on AN/I.) [[User:ST47|ST47]] ([[User talk:ST47|talk]]) 00:25, 7 April 2019 (UTC) == Clarification of [[WP:3RR]] == [[WP:3RR]] says '''An edit or a series of consecutive edits that undoes other editors' actions—whether in whole or in part—counts as a revert.''' <p>{{u|El C}} appears to believe that a user's first edit on a page that undoes other editors' actions—whether in whole or in part— '''''does not''''' count as a revert, which is completely contradictory to that brightline '''policy'''. In fact, making four changes to an article in 24 hours is what famously got {{noping|Winkelvi}} blocked for three months in 2017, and he tried unsuccessfully to argue that he didn't know the first change "counted" [https://en.wikipedia.org/w/index.php?title=User_talk:Winkelvi&oldid=776108087#March_2017]. Can someone please alert El C as to how 3RR works? By the way, {{U|Drmies}} once gave me a lecture via email about the definition of 3RR and how it includes the first change. <p>Also, by the way, could someone look at [[WP:Administrators%27 noticeboard/Edit warring#User:Krimuk2.0 reported by User:Softlavender (Result: No violation)]]? {{U|Krimuk2.0}} is edit-warring (five reverts so far in less than 2 hours [https://en.wikipedia.org/w/index.php?title=Jack_Lowden&diff=891199790&oldid=891189691], [https://en.wikipedia.org/w/index.php?title=Jack_Lowden&diff=next&oldid=891202568], [https://en.wikipedia.org/w/index.php?title=Jack_Lowden&diff=next&oldid=891205115], [https://en.wikipedia.org/w/index.php?title=Jack_Lowden&diff=next&oldid=891206680], [https://en.wikipedia.org/w/index.php?title=Jack_Lowden&diff=next&oldid=891207503]), [[WP:CANVASSING]], and battlegrounding on [[Jack Lowden]], despite a usertalk warning, but El C initially closed it as "no violation", and even with an explanation of and link to 3RR and links to two more reverts by Krimuk2.0, that has not changed. Thank you. [[User:Softlavender|Softlavender]] ([[User talk:Softlavender|talk]]) 12:30, 6 April 2019 (UTC) :It would include the first edit ''if'' a <u>specific</u> editor's edit would be undone, but that is not the case here. It is merely longstanding text which is partially replaced with a new addition, which [https://en.wikipedia.org/w/index.php?title=Wikipedia:Administrators%27_noticeboard/Edit_warring&oldid=891208259#User:Krimuk2.0_reported_by_User:Softlavender_(Result:_No_violation) I do not count] as a revert. [[User:El_C|El_C]] 12:39, 6 April 2019 (UTC) ::What do you mean by "''if'' a <u>specific</u> editor's edit would be undone"? 3RR reads: '''An edit or a series of consecutive edits that undoes other editors' actions—whether in whole or in part—counts as a revert.''' Any change to existing text in an article by definition undoes other editors' actions in whole or in part; there is no getting around that. And 3RR does not specify any loopholes. [[User:Softlavender|Softlavender]] ([[User talk:Softlavender|talk]]) 12:49, 6 April 2019 (UTC) :::A removal of text isn't automatically a revert, is what I'm saying. [[User:El_C|El_C]] 12:56, 6 April 2019 (UTC) ::::For the purposes of 3RR, ''any'' removal of text '''''is indeed''''' a revert; that is precisely why [[WP:3RR]] is worded precisely that way. [[User:Softlavender|Softlavender]] ([[User talk:Softlavender|talk]]) 13:04, 6 April 2019 (UTC) :::::I disagree that that first edit constituted a revert. [[User:El_C|El_C]] 13:07, 6 April 2019 (UTC) :In context, I do not think the first series of edits to the lede constitute a revert. These appear to be bold changes.[[Special:diff/891189691|This]] is a really grating type of revert. Bold changes don't need prior discussion. Beyond this, Krimuk2.0 should self-revert to the status quo. Discuss possible improvements to the lede on the talk page, and then implement them. [[User:Mr rnddude|Mr rnddude]] ([[User talk:Mr rnddude|talk]]) 13:05, 6 April 2019 (UTC) ::{{noping|Mr rnddude}}, [[WP:3RR]] says '''An edit or a series of consecutive edits that undoes other editors' actions—whether in whole or in part—counts as a revert.''' It does not exempt the first time that occurs. You might think it does, but it does not. A change to existing article text undoes other editors' actions by definition; there's no getting around that. [[User:Softlavender|Softlavender]] ([[User talk:Softlavender|talk]]) 13:12, 6 April 2019 (UTC) :::Both [[WP:BOLD]] and [[WP:BRD]] would disagree with the "a change to existing article text" interpretation. From BRD: {{tq|Be bold, and make what you currently believe to be the optimal changes based on your best effort. Your change might involve re-writing, rearranging, adding or removing information}}. Not all removals and changes are reverts. [[User:Mr rnddude|Mr rnddude]] ([[User talk:Mr rnddude|talk]]) 13:45, 6 April 2019 (UTC) :{{nacmt}} I agree with {{np|El_C|Mr rnddude}}'s interpretations and believe the wording of the policy should be amended via RfC if necessary. [[User:Alpha3031|Alpha3031]] ([[User talk:Alpha3031|t]] • [[Special:Contributions/Alpha3031|c]]) 14:44, 6 April 2019 (UTC) ::For what it's worth, I agree with El_C's interpretation as well. A revert is pretty simply defined as an edit that undoes another edit. It has nothing to do with if it's your first edit on the page or not. [https://en.wikipedia.org/w/index.php?title=Jack_Lowden&type=revision&diff=891041920&oldid=889815751 This] is not a revert, it is a change that's being made for the first time. (On the other hand, [https://en.wikipedia.org/w/index.php?title=Jack_Lowden&diff=next&oldid=891041920 Softlavender's first edit in the war] ''does'' count as a revert, because it ''is'' a revert.) Also, [https://en.wikipedia.org/w/index.php?title=Jack_Lowden&diff=next&oldid=891206680] and [https://en.wikipedia.org/w/index.php?title=Jack_Lowden&diff=next&oldid=891207503] definitely aren't reverts. I don't think there's any need for an RfC, as I'd be surprised if you could find any admin who would consider those reverts. [[User:ST47|ST47]] ([[User talk:ST47|talk]]) 22:53, 6 April 2019 (UTC) *No need to ping me about something I may have emailed you at one time, Softlavender. [[User:Drmies|Drmies]] ([[User talk:Drmies|talk]]) 21:20, 6 April 2019 (UTC) == Deletion == The deletion of [[2019 Kashmir airstrikes]] citing A10 suggests [[WP:BIAS]] within this highly controversial topic area. How about deleting [[2019 Balakot airstrike]] for the same reason to make it even? Or better still restore the former and let the AfD decide if someone has issue with its existence. [[Special:Contributions/110.93.250.2|110.93.250.2]] ([[User talk:110.93.250.2|talk]]) 13:15, 6 April 2019 (UTC) : We don't speedy delete articles to score political points. We delete articles according to the [[WP:CSD|criteria for speedy deletion]]. If you want a deleted article to be undeleted, contact the admin who deleted it. If that admin declines to undelete the article, go to [[WP:DRV|deletion review]] and ask there. However, articles about an India-Pakistan conflict are now under [[WP:GS|general sanctions]] that [[WP:GS/IPAK|prohibit anyone from editing them]] unless they are [[WP:XCON|extended confirmed]]. In short, that means no IP editors are allowed to edit an article about a conflict between India and Pakistan. [[User:NinjaRobotPirate|NinjaRobotPirate]] ([[User talk:NinjaRobotPirate|talk]]) 13:50, 6 April 2019 (UTC) == Edit summary vandalism == Would someone please remove the lengthy nonsense edit summary [https://en.wikipedia.org/w/index.php?title=Darkness&type=revision&diff=891105488&oldid=891046233 here], as it remains highly visible in page history[[User:Bhunacat10|: <b style="color:seagreen">Bhunacat10</b>]] [[User talk:Bhunacat10|<span style="color:seagreen"> (talk),</span> ]] 14:36, 6 April 2019 (UTC) :{{done}} —[[User:DoRD|DoRD]] ([[User talk:DoRD|talk]])​ 16:15, 6 April 2019 (UTC) == Range block assist == Hi all, rangeblocks intimidate me. That said, over the last few months (maybe since February) several Indian entertainment articles have been hit by an Indonesia-based vandal. If you look at the edit history of [https://en.wikipedia.org/w/index.php?title=Thapki_Pyar_Ki&action=history Thapki Pyar Ki] and set your results to 500, you'll see the extent of the problem. Some examples:[https://en.wikipedia.org/w/index.php?title=Thapki_Pyar_Ki&diff=891281961&oldid=890983362][https://en.wikipedia.org/w/index.php?title=Thapki_Pyar_Ki&type=revision&diff=890982960&oldid=890596662][https://en.wikipedia.org/w/index.php?title=Thapki_Pyar_Ki&type=revision&diff=890592451&oldid=890210054][https://en.wikipedia.org/w/index.php?title=Thapki_Pyar_Ki&type=revision&diff=888331098&oldid=888271303] Often there's numerical vandalism, changing numbers to 9999 and such. Here are some of the IPs used, in numerical order: * 182.1.66.127 * 182.1.74.209 * 182.1.76.106 * 182.1.77.62 * 182.1.77.195 * 182.1.92.122 * 182.1.94.78 * 182.1.95.23 * 182.1.101.220 * 182.1.102.151 * 182.1.104.10 * 182.1.106.198 * 182.1.123.108 Can someone please set up a rangeblock or two to cover these, if possible? Thanks, [[User:Cyphoidbomb|Cyphoidbomb]] ([[User talk:Cyphoidbomb|talk]]) 00:35, 7 April 2019 (UTC) :Looks like [https://en.wikipedia.org/wiki/Special:Contributions/182.1.64.0/18 182.1.64.0/18] would cover all of that in one block. I don't see much collateral damage in terms of logged out users, have a look at the contribs and see what you think. It looks like [[Thapki Pyar Ki]] is the biggest target, can we just semi-protect that? [[User:ST47|ST47]] ([[User talk:ST47|talk]]) 00:50, 7 April 2019 (UTC) ::{{ping|ST47}} Thanks for the assist on this. It's not just Thapki Pyar Ki, it's [[:Porus (TV series)]], [[:List of programs broadcast by Colors]], [[:Mahakali — Anth Hi Aarambh Hai]] and some others. Also, I don't typically mind leaving a honeypot to make it easier to spot these people. I'm not seeing a whole lot in the way of constructive edits from this range, so I'll likely block. Thanks, [[User:Cyphoidbomb|Cyphoidbomb]] ([[User talk:Cyphoidbomb|talk]]) 01:59, 7 April 2019 (UTC) == Test announcement == The Arbitration Committee has resolved by [[Special:Permalink/890994939#Motion:_India-Pakistan|motion]] that: {{ivmbox|1={{u|SheriffIsInTown}}'s [https://en.wikipedia.org/w/index.php?title=User_talk%3ASheriffIsInTown&type=revision&diff=841345083&oldid=841015004 topic ban] from pages related to conflict between India and Pakistan is lifted, subject to a probationary period lasting six months from the date this motion is enacted. During this period, any uninvolved administrator may re-impose the topic ban as an arbitration enforcement action, subject to appeal only to the Arbitration Committee. If the probationary period elapses without incident, the topic ban is to be considered permanently lifted.}} For the Arbitration Committee, [[User:Bradv|Bradv]] ([[User talk:Bradv|talk]]) 04:18, 7 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Test announcement]]'''<!-- [[User:Arbitration Bot|Arbitration Bot]] ([[User talk:Arbitration Bot|talk]]) 20:15, 7 April 2019 (UTC) --><!--Template:hes--> == Motion: Conduct of Mister Wiki editors 2 == The Arbitration Committee has resolved by [[Special:Permalink/888388781#Motion:_Conduct_of_Mister_Wiki_editors|motion]] that: {{ivmbox|[[Wikipedia:Arbitration/Requests/Case/Conduct of Mister Wiki editors#Salvidrim! prohibited_(II)|Remedy 2.1]] of the {{RFARlinks|Conduct of Mister Wiki editors}} (Salvidrim's prohibition from reviewing articles for creation drafts) is rescinded. He may apply for use of the AfC helper script as usual at [[Wikipedia talk:WikiProject Articles for creation/Participants]]. }} For the Arbitration Committee --[[User:Cameron11598|Cameron<sub><small>11598</small></sub>]] <sup>[[User Talk:Cameron11598|(Talk)]] </sup> 06:33, 19 March 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Motion: Conduct of Mister Wiki editors 2]]'''<!-- [[User:Arbitration Bot|Arbitration Bot]] ([[User talk:Arbitration Bot|talk]]) 20:15, 7 April 2019 (UTC) --><!--Template:hes--> == Test: AN protected == This should continue to retry until AN is unprotected, at which point this page will be updated. [[User:Bradv|Bradv]] ([[User talk:Bradv|talk]]) 20:13, 7 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Test: AN protected]]'''<!-- [[User:Arbitration Bot|Arbitration Bot]] ([[User talk:Arbitration Bot|talk]]) 20:15, 7 April 2019 (UTC) --><!--Template:hes--> == Another test: AN protected == Testing error logging facilities. [[User:Bradv|Bradv]] ([[User talk:Bradv|talk]]) 21:03, 7 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Another test: AN protected]]'''<!-- [[User:Arbitration Bot|Arbitration Bot]] ([[User talk:Arbitration Bot|talk]]) 21:05, 7 April 2019 (UTC) --><!--Template:hes--> == Test: now with a bot flag == This is another test of the crossposting bot. [[User:Bradv|Bradv]] ([[User talk:Bradv|talk]]) 03:25, 8 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Test: now with a bot flag]]'''<!-- [[User:Arbitration Bot|Arbitration Bot]] ([[User talk:Arbitration Bot|talk]]) 03:26, 8 April 2019 (UTC) --><!--Template:hes--> == Test: now with a bot flag 2 == Trying that again. [[User:Bradv|Bradv]] ([[User talk:Bradv|talk]]) 03:25, 8 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Test: now with a bot flag 2]]'''<!-- [[User:Arbitration Bot|Arbitration Bot]] ([[User talk:Arbitration Bot|talk]]) 03:28, 8 April 2019 (UTC) --><!--Template:hes--> == Testing after rename == This is a test after the account was renamed. [[User:Bradv|Bradv]] ([[User talk:Bradv|talk]]) 05:00, 8 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Testing after rename]]'''<!-- [[User:ArbClerkBot|ArbClerkBot]] ([[User talk:ArbClerkBot|talk]]) 05:00, 8 April 2019 (UTC) --><!--Template:hes--> == New test == This is another test. [[User:Bradv|Bradv]] ([[User talk:Bradv|talk]]) 05:22, 8 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#New test]]'''<!-- [[User:ArbClerkBot|ArbClerkBot]] ([[User talk:ArbClerkBot|talk]]) 05:23, 8 April 2019 (UTC) --><!--Template:hes--> == Motion: Palestine-Israel articles 3 == The Arbitration Committee has resolved by [[Special:Permalink/887668566#Motion: Palestine-Israel articles|motion]] that: {{ivmbox|The [[Wikipedia:Requests for arbitration/Palestine-Israel articles#General 1RR restriction|General 1RR prohibition]] of the {{RFARlinks|Palestine-Israel articles}} is amended to read: :Each editor is limited to one revert per page per 24 hours on any page that could be reasonably construed as being related to the Arab-Israeli conflict. Reverts made to enforce the [[WP:ARBPIA3|General Prohibition]] are exempt from the provisions of this motion. Also, the [[WP:3RRNO|normal exemptions apply]]. Editors who violate this restriction may be blocked by any uninvolved administrator, even on a first offense. <ins>This remedy may only be enforced on pages with the {{tl|ARBPIA 1RR editnotice}} edit notice. The community is encouraged to place the {{tl|ARBPIA 1RR editnotice}} on any page that could be reasonably construed as being related to the Arab-Israeli conflict.</ins>}} For the Arbitration Committee, <span style="font-weight:bold;color:darkblue">[[User_talk:Bradv|Brad''v'']]</span>[[Special:Contributions/Bradv|<span style="color:red">🍁</span>]] 02:46, 14 March 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Motion: Palestine-Israel articles 3]]'''<!-- [[User:ArbClerkBot|ArbClerkBot]] ([[User talk:ArbClerkBot|talk]]) 05:26, 8 April 2019 (UTC) --><!--Template:hes--> == Bot testing == This entry will ping [[User:Barkeep49]]. [[User:Bradv|Bradv]] ([[User talk:Bradv|talk]]) 14:13, 8 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Bot testing]]'''<!-- [[User:ArbClerkBot|ArbClerkBot]] ([[User talk:ArbClerkBot|talk]]) 14:15, 8 April 2019 (UTC) --><!--Template:hes--> == Crossposting test == {{ivmbox|This one will attempt to crosspost to [[User:Barkeep49]], but will fail because the talk page is protected. The error will be ignored and the rest of the task will carry out correctly.}} [[User:Bradv|Bradv]] ([[User talk:Bradv|talk]]) 14:44, 8 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Crossposting test]]'''<!-- [[User:ArbClerkBot|ArbClerkBot]] ([[User talk:ArbClerkBot|talk]]) 14:44, 8 April 2019 (UTC) --><!--Template:hes--> == New crosspost test == {{ivmbox|This post should crosspost to {{u|Barkeep49}}, even though the links to his userpage go through a template.}} blah blah blah [[User:Bradv|Bradv]] ([[User talk:Bradv|talk]]) 22:47, 8 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#New crosspost test]]'''<!-- [[User:ArbClerkBot|ArbClerkBot]] ([[User talk:ArbClerkBot|talk]]) 22:47, 8 April 2019 (UTC) --><!--Template:hes--> == Yet another test == This is yet another test. [[User:Bradv|Bradv]] ([[User talk:Bradv|talk]]) 02:52, 11 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Yet another test]]'''<!-- [[User:ArbClerkBot|ArbClerkBot]] ([[User talk:ArbClerkBot|talk]]) 02:53, 11 April 2019 (UTC) --><!--Template:hes-->== Test == This is a test. – <span style="font-weight:bold;color:darkblue">[[User_talk:Bradv|brad''v'']]</span>[[Special:Contributions/Bradv|<span style="color:red">🍁</span>]] 04:17, 15 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Test]]'''<!-- [[User:ArbClerkBot|ArbClerkBot]] ([[User talk:ArbClerkBot|talk]]) 04:19, 15 April 2019 (UTC) --><!--Template:hes--> == Test == This is a test. – <span style="font-weight:bold;color:darkblue">[[User_talk:Bradv|brad''v'']]</span>[[Special:Contributions/Bradv|<span style="color:red">🍁</span>]] 04:17, 15 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Test]]'''<!-- [[User:ArbClerkBot|ArbClerkBot]] ([[User talk:ArbClerkBot|talk]]) 04:23, 15 April 2019 (UTC) --><!--Template:hes--> == [[User:Bradv redirect|Bradv redirect]] test == This edit should post on the redirected talk page for [[User:Bradv redirect]]. – <span style="font-weight:bold;color:darkblue">[[User_talk:Bradv|brad''v'']]</span>[[Special:Contributions/Bradv|<span style="color:red">🍁</span>]] 04:36, 15 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#Bradv redirect test]]'''<!-- [[User:ArbClerkBot|ArbClerkBot]] ([[User talk:ArbClerkBot|talk]]) 04:37, 15 April 2019 (UTC) --><!--Template:hes--> == redirect test 2 == This one should also post on the redirected talk page for [[User:Bradv redirect]]. The double-redirect scenario in the previous test was successful. – <span style="font-weight:bold;color:darkblue">[[User_talk:Bradv|brad''v'']]</span>[[Special:Contributions/Bradv|<span style="color:red">🍁</span>]] 04:41, 15 April 2019 (UTC) : Discuss this at: '''[[Wikipedia talk:Arbitration Committee/Noticeboard#redirect test 2]]'''<!-- [[User:ArbClerkBot|ArbClerkBot]] ([[User talk:ArbClerkBot|talk]]) 04:42, 15 April 2019 (UTC) --><!--Template:hes--> == Request for adminship == I don't know if this is the right place to request this, but may I become a test admin on Test Wikipedia? --[[User:AJ1m3,zsd.|<span style="color:#0000FF">'''AJ'''&nbsp;😃</span>]]&nbsp;(&nbsp;[[User talk:AJ1m3,zsd.|💬]]&nbsp;•&nbsp;[[Special:Contributions/AJ1m3,zsd.|📝]]&nbsp;) 20:54, 2 December 2020 (UTC) == [[User:Fuck|Fuck]] ([[User talk:Fuck|discussion]] - [[Special:Contributions/Fuck|contribution]]) == Delete his user page, please. [[Special:Contributions/79.166.210.218|79.166.210.218]] 12:26, 20 October 2022 (UTC) == Deletion request == Could someone please delete my user page? [[User:ClydeFranklin|ClydeFranklin]] ([[User talk:ClydeFranklin|talk]]) 04:13, 17 September 2023 (UTC) == حريم. بينيكوا بعض == ملط افلام سكس [[Special:Contributions/&#126;2026-26083-05|&#126;2026-26083-05]] ([[User talk:&#126;2026-26083-05|talk]]) 20:24, 29 April 2026 (UTC) blge93qygzt95aoydziyletf0x1ivuz Test page 123 0 106306 739877 446023 2026-04-30T11:34:16Z MPostoronca-WMF 72719 739877 wikitext text/x-wiki foobar testcaptcha 1rolxjjsubsjketz3bhgtyqydvh0m0z 739878 739877 2026-04-30T11:34:32Z MPostoronca-WMF 72719 739878 wikitext text/x-wiki foobar testcaptcha testcaptcha 3ogej66b108fv0kvufp9pk3qflmcg4e Wikipedia:Sandbox 4 107092 739798 739758 2026-04-29T21:00:30Z Cewbot 33876 Clear the sandbox. If you want to keep it longer, please test it in [[Special:MyPage/Sandbox|personal sandbox]], you can also check the revision history of the sandbox. 739798 wikitext text/x-wiki <noinclude>{{Sandbox}}</noinclude> == Please start your testing below this line == 9v37rcaxoiwjar8n3q9n7dcsjdvcyin Helikoptérové peníze 0 107419 739862 584145 2026-04-30T09:56:40Z ~2026-26239-57 73762 739862 wikitext text/x-wiki {{Wikifikovat}} '''Helikoptérové peníze''' jsou navrženou nekonvenční [[Měnová politika|monetární politikou]]{{done}}, v nějakých případech alternativní ke [[Kvantitativní uvolňování|kvantitativnímu uvolňování]]{{done}} (QE), když se ekonomie nachází v tzv. likvidační pasti (když se [[Úroková sazba|úrokové sazby]]{{done}} blíží k nule a ekonomie zůstává v recesi). Ačkoliv původní myšlenka helikoptérových peněz popisovala [[Centrální banka|centrální banky]]{{done}}, které provádějí platby přímo jednotlivcům, ekonomové používali výraz pro označení celé řady různých politických představ, včetně permanentní modernizace rozpočtového schodku – s přídavným elementem, který se pokoušel šokovat domněnky týkající se budoucí inflace nebo nominálního růstu HDP s cílem změnit očekávání.<ref>{{Citace elektronického periodika | titul = Helicopter Money is Permanent | periodikum = Worthwhile Canadian Initiative | url = https://worthwhile.typepad.com/worthwhile_canadian_initi/2016/04/helicopter-money-is-permanent.html | datum přístupu = 2019-11-29 }}</ref> this is an edit Druhý soubor politik je bližší původnímu popisu helikoptérových peněz a je více inovativní v kontextu monetární historie, včetně provádějí přímých převodů centrálními bankami do privátního sektoru financovaného základními penězi bez přímé účasti fiskálních orgánů.<ref>{{Citace periodika | příjmení = Blyth | jméno = Mark | příjmení2 = Lonergan | jméno2 = Eric | příjmení3 = Wren-Lewis | jméno3 = Simon | titul = Now the Bank of England needs to deliver QE for the people | periodikum = The Guardian | datum vydání = 2015-05-21 | issn = 0261-3077 | jazyk = en-GB | url = https://www.theguardian.com/business/economics-blog/2015/may/21/now-the-bank-of-england-needs-to-deliver-qe-for-the-people | datum přístupu = 2019-11-29 }}</ref><ref>{{Citace elektronického periodika | příjmení = Matthews | jméno = Dylan | titul = To fix the economy, let's print money and mail it to everyone | periodikum = Vox | url = https://www.vox.com/2014/9/9/6122517/helicopter-drop-money-print-fed-blyth-lonergan | datum vydání = 2014-09-09 | jazyk = en | datum přístupu = 2019-11-29 }}</ref> Toto bylo také nazýváno jako podíl na zisku občanů(dividendou) nebo distribucí budoucího výnosu z vydávání [[Hotové peníze|hotových peněz]]{{done}} (ražebných).<ref>{{Citace elektronického periodika | příjmení = Bank | jméno = European Central | titul = Interview with La Repubblica | periodikum = European Central Bank | url = https://www.ecb.europa.eu/press/inter/date/2016/html/sp160318.en.html | jazyk = en | datum přístupu = 2019-11-29 }}</ref> Výraz helikoptérové peníze byl poprvé použit [[Milton Friedman|Miltonem Friedmanem]]{{done}} v roce 1969, když psal o podobenství padajících peněz z helikoptéry, aby ilustroval efekty měnové expanze. Tento koncept byl znovu použit ekonomy jako návrh měnové politiky na počátku 21. století při reakci na tzv. „ztracené japonské desetiletí“ (Japan’s Lost Decade). V listopadu roku 2002 [[Ben Bernanke|Ben Bernanke]]{{done}}, později guvernér generální rezervní rady a poté i předseda, navrhl, že by se helikoptérové peníze mohli využít k zabránění deflace. test == Původ == I když podobné koncepty byly použity různými lidmi, například C. H. Douglasem, za zakladatele se považuje vítěz Nobelovy ceny [[Ekonomie|ekonom]]{{maybe}}, economy should be linked somewhere, but I'm not sure if this is the correct place Milton Friedman, který poprvé použil termín „helikoptérové peníze” v dnes již známých novinách The Optimum Quantity of Money" (1969), kde použil toto přirovnání: Představme si, že jednoho dne nad lidmi přeletí helikoptéra a shodí 1000 $, které jsou samozřejmě rychle sesbírány členy komunity. Předpokládejme, že každý ví, že tento jev byl jedinečný a již se nebude opakovat. Tento koncept původně použil Friedman jako ilustraci efektů měnové politiky na inflaci a nákladů na držení peněz, než jako skutečný politický návrh. Později se začal tento koncept stávat čím dál více středem diskuze ekonomů jako vážná alternativa k nástrojům měnové politiky jako je například kvantitativní uvolňování. Podle zastánců tohoto konceptu, by helikoptérové peníze byly mnohem účinnějším způsobem, jak zvýšit [[Agregátní poptávka|agregátní poptávku]]{{done}}, zejména v situaci likviditní pasti, kdy by centrální banky dosáhli tzv. nulové dolní hranice. Samotný Friedman se odvolává na financování převodů plateb s peněžním základem,což považuje za důkaz toho, že měnová politika má pořád sílu, když konvenční politiky selhaly, v tomto případě mluvíme o Pigoově efektu(efekt reálných peněžních zůstatků) v jeho prezidentské adrese AER z roku 1968 <ref>{{Citace elektronického periodika | titul = Wayback Machine | periodikum = web.archive.org | url = https://web.archive.org/web/20160605050134/https://assets.aeaweb.org/assets/production/journals/aer/top20/58.1.1-17.pdf | datum vydání = 2016-06-05 | datum přístupu = 2019-11-29 }}</ref>. Friedman konkrétně tvrdí, že oživení víry v účinnost peněžní politiky bylo silně podporováno mezi ekonomy teoretickým vývojem, kterým byl iniciován Harberlem, však pojmenován Pigou, aby poukázali na kanál, který mění bohatství - prostřednictvím něhož změny reálného množství peněz ovlivňují agregátní poptávky i přestože nemění úrokové sazby. Podle Friedmana je zřejmé, že peníze musí být produkovány „jinými způsoby” než jsou operace na otevřeném trhu, což stejně jako QE zahrnuje prosté nahrazování peněz za jiné aktiva bez změny celkového bohatství. Friedman se odkazuje na dokument od Gottfrieda Harberlera z roku 1952, kde Harberer tvrdí, „Předpokládejme, že množství peněz se zvýší snížením [[Daň|daní]]{{done}} nebo vládními převodními platbami, a výsledný deficit je financován půjčkami z centrální banky nebo prostým tisknutím peněz”.<ref>{{Citace periodika | příjmení = Haberler | jméno = Gottfried | titul = The Pigou Effect Once More | periodikum = Journal of Political Economy | datum vydání = 1952-06-01 | ročník = 60 | číslo = 3 | strany = 240–246 | issn = 0022-3808 | doi = 10.1086/257211 | url = https://www.journals.uchicago.edu/doi/10.1086/257211 | datum přístupu = 2019-11-29 }}</ref> Je pozoruhodné, jak v kontext nedávných debat o rozdělení mezi měnové a [[Fiskální politika|fiskální politiky]]{{done}} viděl Friedman tyto politiky jako důkaz o síle měnové politiky. Na stejné AER adrese je vysoce kritický vůči včasnosti a účinnosti fiskálních opatření ke stabilizaci poptávky. Myšlenka helikoptérového poklesu byla obnovena jako vážný politický návrh okolo roku 2000, kdy se ekonomové chtěli nechat inspirovat od Japonska. Ben Bernanke, guvernér federální rezervní rady, přednesl proslov v listopadu 2002 na téma jak předejít deflaci, kde řekl, že Keynes” jednou skoro vážně navrhnul jako anti-deflační opatření to, že by vláda naplnila lahve s měnou, které by pohřbila je do dolních šacht, a posléze by byla vykopána veřejností.” V tomto proslovu Bernanke sám řekl, „snížení daní financované z peněz je v podstatě rovnocenné vyhození peněz z helikoptéry od Miltona Friedmana.” <ref>{{Citace elektronického periodika | titul = Speech, Bernanke --Deflation-- November 21, 2002 | periodikum = www.federalreserve.gov | url = https://www.federalreserve.gov/boarddocs/Speeches/2002/20021121/default.htm#f8 | datum přístupu = 2019-11-29 }}</ref> V rámci jeho proslovu Bernanke také odkazuje na důležitý dokument Gauti Eggertsona, který zdůrazňuje význam závazku centrálních bank udržet si v budoucnu zásobu peněz.<ref>{{Citace elektronického periodika | titul = How to Fight Deflation in a Liquidity Trap : Committing to Being Irresponsible : IMF Working Paper: How to Fight Deflation in a Liquidity Trap - Committing to Being Irresponsible: | periodikum = www.elibrary.imf.org | url = https://www.elibrary.imf.org/view/IMF001/03171-9781451848588/03171-9781451848588/03171-9781451848588.xml | datum vydání = 2003-03-01 | doi = 10.5089/9781451848588.001 | jazyk = en | datum přístupu = 2019-11-29 }}</ref> Irský ekonom Eric Lonergan také tvrdil v roce 2002 v [[Financial Times|Financial Times]]{{done}}, že centrální banky považují převody peněz do domácností jako alternativu pro další snižování úrokových sazeb a také tlaku na [[Finanční stabilita|finanční stabilitu]].<ref>Beyond interest rates, Financial Times, Sep 09, 2002</ref> V roce 2003, Willem Buiter, poté vedoucí ekonom v Evropské bance pro Rekonstrukci a vývoj, obnovil koncept Helikoptérových peněz v teoretickém dokumentu, kde tvrdil, že peněžní základ není závazek, což poskytuje přísnější argument pro Friedmanovy a Haberlerovy Pigouvianovy intuice. <ref>{{Citace monografie | příjmení = Buiter | jméno = Willem H. | titul = Helicopter Money: Irredeemable Fiat Money and the Liquidity Trap | url = https://ideas.repec.org/p/cpr/ceprdp/4202.html | jazyk = en }}</ref> Od roku 2012, ekonomové nazývali tuto představu jako “kvantitativní uvolňování pro lidi.”<ref name=":0">{{Citace elektronické monografie | příjmení = Kaletsky | jméno = Anatole | titul = Reuters Blogs | url = http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ | datum vydání = 2012-08-01 | datum přístupu = 2019-11-29 }} {{Webarchive|url=https://web.archive.org/web/20130528204341/http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |date=2013-05-28 }} {{Cite web |url=http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |title=Archived copy |access-date=2020-02-17 |archive-date=2013-05-28 |archive-url=https://web.archive.org/web/20130528204341/http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |url-status=dead }}{{Cite web |url=http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |title=Archived copy |access-date=2020-02-17 |archive-date=2013-05-28 |archive-url=https://web.archive.org/web/20130528204341/http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |url-status=dead }} {{Cite web |url=http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |title=Archived copy |access-date=2020-02-17 |archive-date=2013-05-28 |archive-url=https://web.archive.org/web/20130528204341/http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |url-status=dead }}</ref><ref name=":1">{{Citace elektronické monografie | příjmení = Muellbauer | jméno = John | titul = VoxEU.org | url = https://voxeu.org/article/combatting-eurozone-deflation-qe-people | datum vydání = 2014-12-23 | datum přístupu = 2019-11-29 }}</ref> == Politická reakce na světovou finanční krizi == V [[Prosinec 2008|prosince 2008]]{{done}}, Eric Lonergan a [[Martin Wolf|Martin Wolf]]{{done}} navrhli v Financial Times, že by centrální banky prováděli převody peněz přímo domácnostem, aby bojovali s hrozbou světové deflace.<ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = Central banks may soon resort to their most powerful weapons against deflation: the printing press and the "helicopter drop" of money | periodikum = | vydavatel = | url = https://www.ft.com/cms/s/0/d049482c-cb8f-11dd-ba02-000077b07658.html#axzz45kZzwC6m | datum vydání = | url archivu = | datum přístupu = 2019-11-29 }}</ref><ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = The most direct and efficient solution to the economic and financial problems is for central banks to transfer cash directly to the household sector | periodikum = | vydavatel = | url = http://blogs.ft.com/economistsforum/2008/12/central-banks-need-a-helicopter/ | datum vydání = | url archivu = | datum přístupu = 2019-11-29 }}</ref> Okolo roku 2012 někteří ekonomové začali zastávat možný helikoptérový pokles, včetně QE, a možné dluhové jubileum financované monetárním základem.<ref name=":0" /> Tyto návrhy odráželi pocit, že tradiční politiky, včetně QE, selhávaly nebo měly mnoho nepříznivých vlivů - na finanční stabilitu nebo distribuci bohatství a příjmu. V roce 2013, předseda britského úřadu pro finanční službu (FCA), Adair Turner, který byl považován za možného nástupce Mervyna Kinga, guvernéra banky Anglie, tvrdil, že deficit zpeněžení je nejrychlejší cesta, jak se vzpamatovat z [[Velká recese|finanční krize]]{{done}}.<ref>{{Citace elektronického periodika | titul = Debt, Money and Mephistopheles: How do we get out of this mess? | periodikum = FCA | url = https://www.fca.org.uk/news/speeches/debt-money-and-mephistopheles-how-do-we-get-out-mess | datum vydání = 2013-02-06 | jazyk = en | datum přístupu = 2019-11-29 }}</ref> == Realizace == Přestože původní definice helikoptérových peněz popisuje situaci, kde centrální banky distribuují  peníze přímo jednotlivcům, více moderní užití tohoto pojmu odkazuje na další možnosti jako třeba na poskytování univerzálních daňových slev všem domácnostem, financované centrální bankou. Toto bylo například aplikováno v Austrálii v roce 2009<ref>{{Citace periodika | příjmení = Leigh | jméno = Andrew | titul = How Much Did the 2009 Australian Fiscal Stimulus Boost Demand? Evidence from Household-Reported Spending Effects | periodikum = The B.E. Journal of Macroeconomics | datum vydání = 2012 | ročník = 12 | číslo = 1 | issn = 1935-1690 | doi = 10.1515/1935-1690.2035 | url = https://www.degruyter.com/view/j/bejm.2012.12.issue-1/1935-1690.2035/1935-1690.2035.xml | datum přístupu = 2019-11-29 }}</ref> nebo v USA jako Economic Stimulus Act (Ekonomický stimulační akt) v roce 2008. Využití slev na daní vysvětluje, proč někteří považují helikoptérové peníze za fiskální impuls a ne za nástroj měnové politiky. Nicméně helikoptérové peníze nejsou zamýšleny jako trvalý koncept, a proto jsou jinou politickou možností než všeobecným základním příjemem.<ref>{{Citace elektronické monografie | titul = Helicopter money and basic income: friends or foes? {{!}} BIEN | url = https://basicincome.org/news/2017/03/helicopter-money-basic-income-friends-or-foes/ | datum přístupu = 2019-11-29 | jazyk = en-US }} {{Webarchive|url=https://web.archive.org/web/20170407135327/http://basicincome.org/news/2017/03/helicopter-money-basic-income-friends-or-foes/ |date=2017-04-07 }}</ref> Pod přísnou definicí, kde helikoptérové poklesy jsou prosté převody z centrální banky do privátního sektoru financovaného základními penězi. Někteří ekonomové tvrdí, že se tato situace v některých situacích zrealizovala.<ref>{{Citace periodika | příjmení = Khan | jméno = Mehreen | titul = Central banks are already doing the unthinkable - you just don't know it | periodikum = The Telegraph | datum vydání = 2016-03-19 | issn = 0307-1235 | jazyk = en-GB | url = https://www.telegraph.co.uk/business/2016/03/17/central-banks-are-already-doing-the-unthinkable---you-just-dont/ | datum přístupu = 2019-11-29 }}</ref> V roce 2016, [[Evropská centrální banka|Evropská centrální banka]]{{done}} zavedla TLTRO program, který spočívá v půjčování peněz bankám za záporné úrokové sazby, což představuje obnos, který je převáděn do bank. Také používání jiných úrokových sazeb na řadu rezerv vede k situaci, kdy komerční banky budou čelit záporným úrokovým sazbám. A to otevírá další zdroj helikoptérového poklesu - třebaže banky dělají prostředníka.<ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = Central banks have ways to drop money into ailing economies | periodikum = | vydavatel = | url = https://www.ft.com/cms/s/3/914fae3e-dec6-11e5-b072-006d8d362ba3.html#axzz45kZzwC6m | datum vydání = | url archivu = | datum přístupu = 2019-11-29 }}</ref> V případě použití TLTRO v eurozóně by podle některých ekonomů mělo poskytnout legální a administrativně dohledatelné prostředky k zavedení převodů do domácností.<ref>{{Citace elektronické monografie | titul = Helicopter drops reloaded {{!}} Bruegel | url = https://bruegel.org/2016/03/helicopter-drops-reloaded/ | datum přístupu = 2019-11-29 | jazyk = en-US }}</ref> Ekonomik Eric Lonergan argumentoval v roce 2016, že legální helikoptérový pokles v eurozóně by mohl být strukturovaný přes nulový kupon (neustálé půjčky), na který by všichni evropští dospělý občané měli nárok. Půjčky by mohly spravovat způsobilé komerční banky a za určitých podmínek by půjčky byly čistým úrokovým výnosem ve prospěch centrálních bank.<ref>{{Citace elektronické monografie | titul = Philosophy of Money | url = https://www.philosophyofmoney.net/legal-helicopter-drops-in-the-eurozone/ | datum vydání = 2016-02-24 | datum přístupu = 2019-11-29 | jazyk = en-GB }}</ref> Tuto myšlenku oživila v roce 2019 Frances Coppola v její knize The Case for People’s Quantitative Easing (Případ pro kvantitativní uvolnění lidí) a také vědci v Blackrocku, včetně Stanleyho Fischera, a také francouzského ekonoma Jeana Pisani-Ferryho. == Kontroverze == Mnoho ekonomů je toho názoru, že helikoptérové peníze ve formě převodů hotovosti do domácností by neměly být považovány za náhražku fiskální politiky. Vzhledem k tomu, že vládní náklady na vypůjčení jsou extrémně nízké a téměř nulové úrokové sazby, měly by fungovat fiskální pobídky prostřednictvím snížení daní a výdajů na infrastrukturu. Z tohoto hlediska jsou helikoptérové peníze skutečně pojistkou proti selhání fiskální politiky z politických, právnických nebo institucionálních důvodů.<ref>{{Citace elektronické monografie | příjmení = Macro | jméno = Mainly | titul = mainly macro | url = https://mainlymacro.blogspot.com/2016/03/two-related-confusions-about-helicopter.html | datum vydání = 2016-03-01 | datum přístupu = 2019-11-29 }}</ref> === Odlišnosti od kvantitativního uvolňování === Stejně jako všechny expanzivní měnové politiky i kvantitativní uvolňování (QE) a helikoptérové peníze zahrnují výrobu peněz centrálními bankami za účelem šíření peněz. Nicméně dopad na rovnováhu rozvah (bank balance sheet) centrálních bank helikoptérovými penězi se liší od kvantitativním uvolňováním. Pod QE si centrální banky vytvářejí rezervy tím, že nakupují dluhopisy nebo jiné finanční aktiva, provádějí tzv. Asset swap (výměna aktiv). Naproti tomu s helikoptérovými penězi, centrální banky rozdávají vytvořené peníze bez toho aniž by navyšovali aktiva na jejich rozvahách. Ekonomové tvrdí, že účinek je od očekávání jiný, protože vytvořené helikoptérové peníze by byly vnímány jako trvalé - což je více nezvratné než kvantitativní uvolňování. Ekonomové poukazují na to, že efekt není příliš odlišný od kombinace expanzivní měnové politiky a expanzivní fiskální politiky. === Implikace pro rozvahy centrálních bank === Jednou z hlavních obav při převodech z centrálních bank přímo do soukromého sektoru je, že na rozdíl od běžných operací na [[Volný trh|volném trhu]]{{done}} nemá centrální banka aktivum odpovídající vytvořeným základním penězům. Pro měřený kapitál centrální banky z toho plynou důsledky, protože základní peníze se obvykle považují za závazek, ale mohlo by to také omezit schopnost centrální banky stanovit úrokové sazby v budoucnu. Účetní zacházení centrálních bank s rozvahami je poměrně kontroverzní.<ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = Governments can borrow without increasing their debt | periodikum = | vydavatel = | url = http://blogs.ft.com/economistsforum/2012/06/governments-can-borrow-without-increasing-their-debt/ | datum vydání = | url archivu = | datum přístupu = 2019-11-30 }}</ref> Většina ekonomů je toho názoru, že kapitál centrálních bank není příliš důležitý.<ref>{{Citace periodika | příjmení = European Central Bank (Frankfurt | jméno = Main) | titul = Working paper series | periodikum = Working paper series | datum vydání = 1999 | issn = 1725-2806 | poznámka = OCLC: 890617800 | jazyk = English | url = https://www.worldcat.org/title/working-paper-series/oclc/890617800 | datum přístupu = 2019-11-29 }}</ref> To na čem opravdu záleží je, zda může být v budoucnu expanze základních peněz zvrácena nebo zda se jedná o jejich další prostředky ke zvýšení úrokové sazby. Bylo navrženo mnoho řešení. Oxfordský profesor, Simon Wren- Lewis navrhl, aby se vláda předběžně zavázala poskytovat dluhopisy v případě potřeby banky Anglie. Evropská centrální banka může nařídit navýšení svého kapitálu a zavedení odstupňovaných rezerv, a z úroků z rezerv poskytuje centrálním bankám řadu nástrojů na ochranu jejich vlastního čistého příjmu a poptávky po rezervách. <ref>{{Citace elektronické monografie | příjmení = Lonergan | jméno = Eric | titul = Philosophy of Money | url = https://www.philosophyofmoney.net/does-the-central-banks-balance-sheet-matter/ | datum vydání = 2015-05-25 | datum přístupu = 2019-11-29 | jazyk = en-GB }}</ref> == Příznivci == Bývalý předseda [[Federální rezervní systém|federální rezervní banky]]{{done}} Ben Bernanke je jedním ze známých zastánců helikoprérových peněz. V listopadu roku 2002 ve svém projevu o Japonsku pronesl, že státem financované daňové úlevy jsou ve své podstatě ekvivalent Friedmanova známého konceptu helikoptérových peněz<ref>{{Citace elektronického periodika | titul = Speech, Bernanke --Deflation-- November 21, 2002 | periodikum = www.federalreserve.gov | url = https://www.federalreserve.gov/boarddocs/Speeches/2002/20021121/default.htm | datum přístupu = 2019-11-29 }}</ref>. V dubnu roku 2016 Ben Bernanke napsal článek ve kterém uvedl, že takové plány jsou tou nejlepší možnou alternativou a bylo by nerozumné je ignorovat.<ref>{{Citace elektronické monografie | příjmení = Bernanke | jméno = Ben S. | titul = Brookings | url = https://www.brookings.edu/blog/ben-bernanke/2016/04/11/what-tools-does-the-fed-have-left-part-3-helicopter-money/ | datum vydání = 2016-04-11 | datum přístupu = 2019-11-29 | jazyk = en-US }}</ref> Federální předsedkyně [[Janet Yellenová|Janet Yellen]]{{done}} také uznala, že helikoptérové peníze by mohly představovat možnost v extrémních případech.<ref>{{Citace elektronického periodika | příjmení = Gillespie | jméno = Patrick | titul = Janet Yellen: Helicopter money is an option in extreme situations | periodikum = CNNMoney | url = https://money.cnn.com/2016/06/16/news/economy/federal-reserve-janet-yellen-helicopter-money/index.html | datum vydání = 2016-06-16 | datum přístupu = 2019-11-29 }}</ref> Federální ředitel a ekonom Willem Buiter je také prominentním advokátem tohoto konceptu.<ref>{{Citace periodika | příjmení = Seith | jméno = Anne | titul = Operation Helicopter: Could Free Money Help the Euro Zone? | periodikum = Spiegel Online | datum vydání = 2015-01-06 | url = https://www.spiegel.de/international/business/economists-say-handing-out-cash-could-help-euro-zone-economy-a-1011352.html | datum přístupu = 2019-11-29 }}</ref> Mezi jiné příznivce patří Martin Wolf<ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = The case for helicopter money | periodikum = | vydavatel = | url = https://www.ft.com/cms/s/0/9bcf0eea-6f98-11e2-b906-00144feab49a.html#axzz2vaE3myVK | datum vydání = | url archivu = | datum přístupu = 2019-11-30 }}</ref>, oxfordský ekonom John Muellbauer<ref name=":1" />,  Simon Wren-Lewis, ekonom Steve Keen, ekonom Mark Blyth z Brown University, profesor ekonomie a bývalý pokladní poradce Brad DeLong<ref>{{Citace elektronického periodika | příjmení = DeLong | jméno = J. Bradford | titul = Rescue Helicopters for Stranded Economies {{!}} by J. Bradford DeLong | periodikum = Project Syndicate | url = https://www.project-syndicate.org/commentary/helicopter-money-fiscal-stimulus-by-j--bradford-delong-2016-04 | datum vydání = 2016-04-29 | jazyk = en | datum přístupu = 2019-11-29 }}</ref><ref>{{Citace elektronického periodika | příjmení = Delong | jméno = J. Bradford | titul = Helicopter Money: When Zero Just Isn’t Low Enough | periodikum = Milken Institute Review | url = http://www.milkenreview.org/articles/helicopter-money-when-zero-just-isnt-low-enough | jazyk = en-US | datum přístupu = 2019-11-29 }}</ref>, UCLA profesor ekonomie Roger Farmer, Ray Dalio, irský ekonom a pokladní manažer Eric Lonergan<ref>{{Citace monografie | titul = Why the ECB should give money directly to People (Eric Lonergan) | url = https://www.youtube.com/watch?v=5EA2nKqcjtg | jazyk = cs-CZ }}</ref>, Anatole Kaletsky<ref>{{Citace elektronického periodika | příjmení = Kaletsky | jméno = Anatole | titul = Central Banking’s Final Frontier? {{!}} by Anatole Kaletsky | periodikum = Project Syndicate | url = https://www.project-syndicate.org/onpoint/central-bankings-final-frontier-by-anatole-kaletsky-2016-05 | datum vydání = 2016-05-06 | jazyk = en | datum přístupu = 2019-11-29 }}</ref>, Romain Baeriswyl<ref>{{Citace monografie | příjmení = Baeriswyl | jméno = Romain | titul = The Case for the Separation of Money and Credit | url = https://doi.org/10.1007/978-3-319-56261-2_6 | editoři = Frank Heinemann, Ulrich Klüh, Sebastian Watzka | vydavatel = Springer International Publishing | místo = Cham | strany = 105–121 | isbn = 978-3-319-56261-2 | doi = 10.1007/978-3-319-56261-2_6 | poznámka = DOI: 10.1007/978-3-319-56261-2_6 | jazyk = en }}</ref>, Martin Sandbu<ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = Helicopter money: if not now, then when? | periodikum = | vydavatel = | url = https://www.ft.com/content/1dc5b4a8-57da-11e6-9f70-badea1b336d4 | datum vydání = | url archivu = | datum přístupu = 2019-11-30 }}</ref>, Jean Pisani-Ferry<ref>{{Citace elektronického periodika | příjmení = Pisani-Ferry | jméno = Jean | titul = How to Ward Off the Next Recession {{!}} by Jean Pisani-Ferry | periodikum = Project Syndicate | url = https://www.project-syndicate.org/commentary/europe-next-recession-alternative-policies-by-jean-pisani-ferry-2019-09 | datum vydání = 2019-09-30 | jazyk = en | datum přístupu = 2019-11-29 }}</ref>. Myšlenka helikoptérových peněz má své podporovatele také mezi guvernéry centrálních bank. Například bývalý předseda irské centrální banky Patrick Honohan uvedl, že věří že by [[Zakázané moře|tato politika]]{{not done}}, unrelated mohla fungovat. Vrchní ekonom ECB Peter Praet jednou řekl, že by se tímto mohly řídit všechny centrální banky. Místoguvernér České centrální banky [[Mojmír Hampl|Mojmír Hampl]]{{done}} publikoval práci, ve které napsal že tato myšlenka nabízí mnoho výhod v porovnání s jinými formami nekonvenčních měnových politik, zejména eliminuje potřebu spoléhat se na komplikovaný přenosný mechanismus, což umožňuje mnohem snazší komunikaci s veřejností a posílení důvěry spotřebitelů, když je to nejvíce potřeba.<ref>{{Citace periodika | příjmení = Hampl | jméno = Mojmir | příjmení2 = Havranek | jméno2 = Tomas | titul = Central Bank Capital as an Instrument of Monetary Policy | datum vydání = 2018 | url = https://www.econstor.eu/handle/10419/176828 | datum přístupu = 2019-11-29 }}</ref> Bill Gross, manažer portfolia Globálního dluhopisového fondu Janus Global Unconstrained Bond Fund, také podporuje implementaci základního příjmu v podobě helikoptérových peněz.<ref>{{Citace elektronické monografie | příjmení = Vigna | jméno = Paul | titul = WSJ | url = https://blogs.wsj.com/moneybeat/2016/05/04/bill-gross-what-to-do-after-the-robots-take-our-jobs/ | datum vydání = 2016-05-04 | datum přístupu = 2019-11-29 | jazyk = en-US }}</ref>V srpnu 2019 prominentní centrální bankéři [[Stanley Fischer|Stanley Fischer]] a Philip Hildebrand napsali dokument vydaný společností BlackRock<ref>{{Citace elektronického periodika | titul = How central banks might deal with the next downturn | periodikum = BlackRock Blog | url = https://www.blackrockblog.com/2019/08/20/how-central-banks-might-deal-with-the-next-downturn/ | datum vydání = 2019-08-20 | jazyk = en-US | datum přístupu = 2019-11-29 }}</ref>, v němž navrhují formu helikoptérových peněz. == Kritika == === Inflační efekt === V minulosti byl nápad helikoptérových peněz zamítnut, protože se předpokládalo, že nevyhnutelně povede k hyperinflaci. V důsledku toho vyplula na povrch řada obav a to hlavně, že helikoptérové peníze by narušily důvěru v měnu (což by nakonec vedlo k hyperinflaci). Toto znepokojení vyslovil zejména německý ekonom (a bývalý hlavní ekonom v ECB) Otmar Issing v příspěvku z roku 2014.<ref>{{Citace elektronického periodika | titul = Die letzte Waffe - Helicopter Money? - boersen-zeitung.de | periodikum = www.boersen-zeitung.de | url = https://www.boersen-zeitung.de/index.php?li=1&artid=2015026026 | jazyk = de | datum přístupu = 2019-11-29 }} {{Cite web |title=Archived copy |url=https://www.boersen-zeitung.de/index.php?li=1&artid=2015026026 |access-date=2020-02-17 |archive-date=2020-06-11 |archive-url=https://web.archive.org/web/20200611172923/https://www.boersen-zeitung.de/index.php?li=1&artid=2015026026 }}</ref> Později v roce 2016 v rozhovoru prohlásil: „Myslím, že celá myšlenka na helikoptérové peníze je naprosto devastující. To není nic jiného než prohlášení o bankrotu měnové politiky.“<ref>{{Citace periodika | titul = Ex-EZB-Chefvolkswirt Issing: „Helikoptergeld“ wäre Bankrotterklärung | issn = 0174-4909 | jazyk = de | url = https://www.faz.net/1.4141309 | datum přístupu = 2019-11-29 }}</ref> Richard Koo uvádí podobný argument<ref>{{Citace elektronického periodika | periodikum = ftalphaville.ft.com | url = https://ftalphaville.ft.com/2016/07/27/2170980/koo-on-why-helicopter-money-just-wont-work/ | datum přístupu = 2019-11-29 }}</ref> , řekl: „pokud by takové obálky dorazily den co den, celá země by rychle upadla do paniky, protože lidé se nevyznají jakou hodnotu tedy jejich měna má.“ To je v rozporu s argumentem, že lidé nebudou utrácet hodně peněz, které dostanou (a proto helikoptérové nemohou být inflační). === Mohly by být helikoptérové peníze utraceny? === Několik prominentních ekonomů, jako je centrální bankéř Raghuram Rajan, jsou proti helikoptérovým penězům z důvodu, že by nebyly účinné, protože lidé by peníze neutratili.<ref>{{Citace periodika | příjmení = Cassidy | jméno = John | titul = Raghuram Rajan and the Dangers of Helicopter Money | datum vydání = 2016-05-13 | issn = 0028-792X | jazyk = en | url = https://www.newyorker.com/news/john-cassidy/raghuram-rajan-and-the-dangers-of-helicopter-money | datum přístupu = 2019-11-29 }}</ref> Lord Adair Turner v reakci na to tvrdí: „Deficity financované penězi budou vždy stimulovat nominální poptávku. Ve srovnání s tím deficity zadlužení mohou udělat, ale nemusí.“<ref>{{Citace elektronického periodika | titul = Why a future tax on bank credit intermediation does not offset the stimulative effect of money finance deficits | periodikum = Institute for New Economic Thinking | url = https://www.ineteconomics.org/research/research-papers/why-a-future-tax-on-bank-credit-intermediation-does-not-offset-the-stimulative-effect-of-money-finance-deficits | jazyk = en | datum přístupu = 2019-11-29 }}</ref> === „Neexistuje nic jako oběd zdarma“ === Další řada kritiků přichází s myšlenkou, že nemůže existovat něco jako „volné peníze“ nebo jak ekonomové říkají „neexistuje nic jako oběd zdarma“. Tuto kritiku vyjádřila zejména skupina vědců pro mezinárodní vypořádání Claudio Borio, Piti Disyatat, Anna Zabai.<ref>{{Citace elektronického periodika | titul = 538 - Will helicopter money be spent? New evidence - De Nederlandsche Bank | periodikum = www.dnb.nl | url = https://www.dnb.nl/en/news/dnb-publications/dnb-working-papers-series/dnb-working-papers/working-papers-2016/dnb350283.jsp | datum přístupu = 2019-11-29 }} {{Cite web |title=Archived copy |url=https://www.dnb.nl/en/news/dnb-publications/dnb-working-papers-series/dnb-working-papers/working-papers-2016/dnb350283.jsp |access-date=2020-02-17 |archive-date=2020-02-17 |archive-url=https://web.archive.org/web/20200217130321/https://www.dnb.nl/en/news/dnb-publications/dnb-working-papers-series/dnb-working-papers/working-papers-2016/dnb350283.jsp }}</ref> , která tvrdila, že helikoptérový pokles by pro občany nutně vyžadoval, aby centrální banka platila úroky z dodávaných zvláštních rezerv. V reakci na to bývalý ekonom MMF Biagio Bossone zpochybňuje pozdější předpoklad<ref>{{Citace elektronického periodika | titul = Why Helicopter Money is a “Free Lunch” {{!}} EconoMonitor | periodikum = archive.economonitor.com | url = http://archive.economonitor.com/blog/2016/06/why-helicopter-money-is-a-free-lunch/ | datum přístupu = 2019-11-29 }} {{Webarchive|url=https://web.archive.org/web/20191105143401/http://archive.economonitor.com/blog/2016/06/why-helicopter-money-is-a-free-lunch/ |date=2019-11-05 }} {{Cite web |title=Archived copy |url=http://archive.economonitor.com/blog/2016/06/why-helicopter-money-is-a-free-lunch/ |access-date=2020-02-17 |archive-date=2019-11-05 |archive-url=https://web.archive.org/web/20191105143401/http://archive.economonitor.com/blog/2016/06/why-helicopter-money-is-a-free-lunch/ }}</ref> a tvrdí, že „helikoptérové peníze jsou„ oběd zdarma “ nebo-li, že pokud to funguje a podaří se mu překonat mezeru výstupu, lidé ji nebudou muset splatit vyššími daněmi nebo nežádoucí (nad optimální) inflací. === Zákonnost === Jiní kritici tvrdí, že helikoptérové peníze by byly mimo mandát centrálních bank, protože by to „setřelo hranice mezi fiskální politikou a měnovou politikou“<ref>{{Citace elektronického periodika | příjmení = Bank | jméno = European Central | titul = Interview with Süddeutsche Zeitung | periodikum = European Central Bank | url = https://www.ecb.europa.eu/press/inter/date/2016/html/sp160926_3.en.html | jazyk = en | datum přístupu = 2019-11-29 }}</ref> převážně z toho důvodu, že helikoptérové peníze by zahrnovaly „fiskální účinky“, což je tradičně úloha vlád. Zastáncové helikoptérových peněz, jako jsou Eric Lonergan a Simon Wren-Lewis, však vyvracejí tento argument tím, že standardní nástroje měnové politiky mají také fiskální dopady.<ref>{{Citace elektronické monografie | příjmení = Macro | jméno = Mainly | titul = mainly macro | url = https://mainlymacro.blogspot.com/2016/05/helicopter-money-and-fiscal-policy.html | datum vydání = 2016-05-20 | datum přístupu = 2019-11-29 }}</ref><ref>{{Citace elektronické monografie | titul = Philosophy of Money | url = https://www.philosophyofmoney.net/the-distinction-between-monetary-and-fiscal-policy/ | datum vydání = 2016-04-03 | datum přístupu = 2019-11-29 | jazyk = en-GB }}</ref> Evropská centrální banka v dopise europoslanci Jonásovi Fernándezovi<ref>{{Citace elektronického periodika | příjmení = Jourdan | jméno = Stan | titul = ECB confirms 'helicopter money' is legally feasible under conditions | periodikum = Positive Money Europe | url = https://www.positivemoney.eu/2016/12/ecb-confirms-helicopter-money-legally-feasible/ | datum vydání = 2016-12-07 | jazyk = en-GB | datum přístupu = 2019-11-29 }}</ref> vysvětlila, že „právní složitost by stále mohla nastat, pokud by se na tento systém mohlo nahlížet jako na ECB, která financuje závazek [[Veřejná správa|veřejného sektoru]] {{done}}vůči třetím stranám, protože by to porušilo také zákaz měnového financování “. Jedná se však o velmi nepravděpodobný a nežádoucí případ, kdy by výplaty helikoptérových peněz centrální bankou nahrazovaly platby za sociální zabezpečení (které jsou odpovědností vlád). === Účetní problémy === Prezident Bundesbank Jens Weidmann také vyjádřil opozici proti helikoptérovým penězům a tvrdil, že by to „roztrhlo mezery v rozvahách centrálních bank. Nakonec by náklady nesly země [[Eurozóna|eurozóny]]{{done}}, a tedy daňoví poplatníci, protože by centrální banky po nějakou dobu neprofitovaly. “<ref>{{Citace elektronického periodika | titul = Deutsche Bundesbank - Interviews - "Helicopter money would tear gaping holes in central bank balance sheets" | periodikum = web.archive.org | url = https://web.archive.org/web/20160518205952/http://www.bundesbank.de/Redaktion/EN/Interviews/2016_01_29_weidmann_funke_mediengruppe.html | datum vydání = 2016-05-18 | datum přístupu = 2019-11-29 }}</ref> Belgická národní banka také vydala dokument, který uvádí podobný argument.<ref>{{Citace elektronického periodika | titul = Helicopter money and debt-financed fiscal stimulus: one and the same thing? {{!}} nbb.be | periodikum = www.nbb.be | url = https://www.nbb.be/en/articles/helicopter-money-and-debt-financed-fiscal-stimulus-one-and-same-thing | datum přístupu = 2019-11-29 }}</ref> == V eurozóně == Od 10. března 2016 se tento nápad v Evropě stává stále populárnějším poté, co [[Mario Draghi|Mario Draghi]]{{done}}, prezident Evropské centrální banky, na [[Tisková konference|tiskové konferenci]]{{done}} uvedl, že tento koncept považuje za „velmi zajímavý“. [59] Po tomto prohlášení následovalo další prohlášení ECB Petera Praeta, který prohlásil: [60] „Ano, všechny centrální banky to dokážou. Můžete vydat měnu a rozdělit ji lidem. To jsou helikoptérové peníze. Helikoptérové peníze dávají lidem část [[Čistá současná hodnota|čisté současné hodnoty]]{{done}} vaší budoucí seigniorage, zisk, který vyděláte v budoucnosti. Otázka je, zda a kdy je vhodné použít nástroj, který je opravdu extrémním nástrojem. ““ V roce 2015 byla zahájena evropská kampaň nazvaná „Kvantitativní uvolňování pro lidi“<ref>{{Citace elektronického periodika | titul = Handtekeningenactie economen tegen ECB | periodikum = fd.nl | url = https://fd.nl/economie-politiek/1129670/handtekeningenactie-voor-helikoptergeld | datum přístupu = 2019-11-30 }}</ref>, která účinně propaguje koncept helikoptérových peněz, spolu s dalšími návrhy na „zelené kvantitativní uvolňování“ a „strategické kvantitativní uvolňování“, které jsou dalšími typy operací měnového financování centrálními bankami zapojujícími programy veřejných investic. Kampaň v současné době podporuje 20 organizací v celé Evropě a více než 116 ekonomů.<ref>{{Citace elektronického periodika | titul = Positive Money Europe | periodikum = Positive Money Europe | url = https://www.positivemoney.eu/ | jazyk = en-GB | datum přístupu = 2019-11-30 }}</ref> Dne 17. června 2016 podepsalo 18 poslanců Evropského parlamentu (včetně Philippa Lambertse, Paula Tanga a Fabia De Masiho) otevřený dopis<ref>{{Citace elektronického periodika | titul = Positive Money Europe | periodikum = Positive Money Europe | url = https://www.positivemoney.eu/ | jazyk = en-GB | datum přístupu = 2019-11-30 }}</ref> , v němž vyzívá Evropská Centrální Banka, aby „poskytla důkazy podloženou analýzu potenciálních účinků alternativních návrhů  uvedené výše a aby objasnili, za jakých podmínek by bylo jejich provedení legální. “ Pokud neuvažuje o alternativách k QE, europoslanci se obávají, že by se Evropská Centrální Banka „nepřipravila na zhoršení ekonomických podmínek“<ref>{{Citace elektronického periodika | titul = Subscribe to read | periodikum = Financial Times | url = https://www.ft.com/content/c5d08c5c-339c-11e6-bda0-04585c31b153 | jazyk = en-GB | datum přístupu = 2019-11-30 }}</ref>. V říjnu 2016 průzkum ukázal, že 54% Evropanů si myslí, že helikoptérové peníze by byly dobrým nápadem, pouze 14% bylo proti.<ref>{{Citace elektronického periodika | titul = Positive Money Europe | periodikum = Positive Money Europe | url = https://www.positivemoney.eu/ | jazyk = en-GB | datum přístupu = 2019-11-30 }}</ref> == V Japonsku == Na setkání s japonským premiérem Shinzo Abe a japonskou bankou Haruhiko Kuroda v červenci 2016<ref name=":2">{{Citace elektronického periodika | titul = Can 'Helicopter Ben Bernanke' Save Japan? | periodikum = The Fiscal Times | url = http://www.thefiscaltimes.com/Columns/2016/07/12/Can-Helicopter-Ben-Bernanke-Save-Japan | jazyk = en | datum přístupu = 2019-11-30 }}</ref> bylo prohlášeno<ref name=":2" />, že bývalý předseda federální rezervy Ben Bernanke doporučoval politiku zpeněžování většího vládního dluhu vytvořeného na financování projektů v oblasti [[Infrastruktura|infrastruktury]]{{done}},<ref>{{Citace elektronického periodika | titul = Bloomberg - Are you a robot? | periodikum = www.bloomberg.com | url = https://www.bloomberg.com/tosv2.html?vid=&uuid=08ad97e0-1305-11ea-ae53-f972e876bd5e&url=L25ld3MvYXJ0aWNsZXMvMjAxNi0wNy0yNy9hYmUtcy11bnVzdWFsLXN0aW11bHVzLWFubm91bmNlbWVudC1tYXktYmUtZGlyZWN0ZWQtYXQtdGhlLWJvag== | datum přístupu = 2019-11-30 }}</ref> jako způsob, jak upustit „helikoptérové peníze" na Japonsko, aby stimuloval ekonomiku a zastavil deflaci v Japonsku. Finanční trhy začaly tuto iniciativu stimulovat dopředu, než byly poprvé ohlášeny Bernankeho návštěvy Japonska. O měsíc později bylo prohlášeno, že Japonská centrální banka, která právě provádí přezkum svého měnového stimulačního programu, zvažuje politiky poněkud podobné „helikoptérovým penězům“, jako je prodej 50 letých nebo věčných dluhopisů.<ref>{{Citace periodika | příjmení = White | jméno = Stanley | titul = 'Helicopter money' talk takes flight as Bank of Japan runs out of runway | periodikum = The Japan Times Online | datum vydání = 2016-07-31 | issn = 0447-5763 | jazyk = en-US | url = https://www.japantimes.co.jp/news/2016/07/31/business/economy-business/helicopter-money-talk-takes-flight-bank-japan-runs-runway/ | datum přístupu = 2019-11-30 }}</ref> == Reference == <references /> {{Portály|Ekonomie}} [[Kategorie:Měnová politika]] 024gs1gk11t4etelxi91phzvvldukxk 739863 739862 2026-04-30T09:57:30Z ~2026-26239-57 73762 showcaptcha 739863 wikitext text/x-wiki {{Wikifikovat}} '''Helikoptérové peníze''' jsou navrženou nekonvenční [[Měnová politika|monetární politikou]]{{done}}, v nějakých případech alternativní ke [[Kvantitativní uvolňování|kvantitativnímu uvolňování]]{{done}} (QE), když se ekonomie nachází v tzv. likvidační pasti (když se [[Úroková sazba|úrokové sazby]]{{done}} blíží k nule a ekonomie zůstává v recesi). Ačkoliv původní myšlenka helikoptérových peněz popisovala [[Centrální banka|centrální banky]]{{done}}, které provádějí platby přímo jednotlivcům, ekonomové používali výraz pro označení celé řady různých politických představ, včetně permanentní modernizace rozpočtového schodku – s přídavným elementem, který se pokoušel šokovat domněnky týkající se budoucí inflace nebo nominálního růstu HDP s cílem změnit očekávání.<ref>{{Citace elektronického periodika | titul = Helicopter Money is Permanent | periodikum = Worthwhile Canadian Initiative | url = https://worthwhile.typepad.com/worthwhile_canadian_initi/2016/04/helicopter-money-is-permanent.html | datum přístupu = 2019-11-29 }}</ref> this is an edit af edit Druhý soubor politik je bližší původnímu popisu helikoptérových peněz a je více inovativní v kontextu monetární historie, včetně provádějí přímých převodů centrálními bankami do privátního sektoru financovaného základními penězi bez přímé účasti fiskálních orgánů.<ref>{{Citace periodika | příjmení = Blyth | jméno = Mark | příjmení2 = Lonergan | jméno2 = Eric | příjmení3 = Wren-Lewis | jméno3 = Simon | titul = Now the Bank of England needs to deliver QE for the people | periodikum = The Guardian | datum vydání = 2015-05-21 | issn = 0261-3077 | jazyk = en-GB | url = https://www.theguardian.com/business/economics-blog/2015/may/21/now-the-bank-of-england-needs-to-deliver-qe-for-the-people | datum přístupu = 2019-11-29 }}</ref><ref>{{Citace elektronického periodika | příjmení = Matthews | jméno = Dylan | titul = To fix the economy, let's print money and mail it to everyone | periodikum = Vox | url = https://www.vox.com/2014/9/9/6122517/helicopter-drop-money-print-fed-blyth-lonergan | datum vydání = 2014-09-09 | jazyk = en | datum přístupu = 2019-11-29 }}</ref> Toto bylo také nazýváno jako podíl na zisku občanů(dividendou) nebo distribucí budoucího výnosu z vydávání [[Hotové peníze|hotových peněz]]{{done}} (ražebných).<ref>{{Citace elektronického periodika | příjmení = Bank | jméno = European Central | titul = Interview with La Repubblica | periodikum = European Central Bank | url = https://www.ecb.europa.eu/press/inter/date/2016/html/sp160318.en.html | jazyk = en | datum přístupu = 2019-11-29 }}</ref> Výraz helikoptérové peníze byl poprvé použit [[Milton Friedman|Miltonem Friedmanem]]{{done}} v roce 1969, když psal o podobenství padajících peněz z helikoptéry, aby ilustroval efekty měnové expanze. Tento koncept byl znovu použit ekonomy jako návrh měnové politiky na počátku 21. století při reakci na tzv. „ztracené japonské desetiletí“ (Japan’s Lost Decade). V listopadu roku 2002 [[Ben Bernanke|Ben Bernanke]]{{done}}, později guvernér generální rezervní rady a poté i předseda, navrhl, že by se helikoptérové peníze mohli využít k zabránění deflace. test == Původ == I když podobné koncepty byly použity různými lidmi, například C. H. Douglasem, za zakladatele se považuje vítěz Nobelovy ceny [[Ekonomie|ekonom]]{{maybe}}, economy should be linked somewhere, but I'm not sure if this is the correct place Milton Friedman, který poprvé použil termín „helikoptérové peníze” v dnes již známých novinách The Optimum Quantity of Money" (1969), kde použil toto přirovnání: Představme si, že jednoho dne nad lidmi přeletí helikoptéra a shodí 1000 $, které jsou samozřejmě rychle sesbírány členy komunity. Předpokládejme, že každý ví, že tento jev byl jedinečný a již se nebude opakovat. Tento koncept původně použil Friedman jako ilustraci efektů měnové politiky na inflaci a nákladů na držení peněz, než jako skutečný politický návrh. Později se začal tento koncept stávat čím dál více středem diskuze ekonomů jako vážná alternativa k nástrojům měnové politiky jako je například kvantitativní uvolňování. Podle zastánců tohoto konceptu, by helikoptérové peníze byly mnohem účinnějším způsobem, jak zvýšit [[Agregátní poptávka|agregátní poptávku]]{{done}}, zejména v situaci likviditní pasti, kdy by centrální banky dosáhli tzv. nulové dolní hranice. Samotný Friedman se odvolává na financování převodů plateb s peněžním základem,což považuje za důkaz toho, že měnová politika má pořád sílu, když konvenční politiky selhaly, v tomto případě mluvíme o Pigoově efektu(efekt reálných peněžních zůstatků) v jeho prezidentské adrese AER z roku 1968 <ref>{{Citace elektronického periodika | titul = Wayback Machine | periodikum = web.archive.org | url = https://web.archive.org/web/20160605050134/https://assets.aeaweb.org/assets/production/journals/aer/top20/58.1.1-17.pdf | datum vydání = 2016-06-05 | datum přístupu = 2019-11-29 }}</ref>. Friedman konkrétně tvrdí, že oživení víry v účinnost peněžní politiky bylo silně podporováno mezi ekonomy teoretickým vývojem, kterým byl iniciován Harberlem, však pojmenován Pigou, aby poukázali na kanál, který mění bohatství - prostřednictvím něhož změny reálného množství peněz ovlivňují agregátní poptávky i přestože nemění úrokové sazby. Podle Friedmana je zřejmé, že peníze musí být produkovány „jinými způsoby” než jsou operace na otevřeném trhu, což stejně jako QE zahrnuje prosté nahrazování peněz za jiné aktiva bez změny celkového bohatství. Friedman se odkazuje na dokument od Gottfrieda Harberlera z roku 1952, kde Harberer tvrdí, „Předpokládejme, že množství peněz se zvýší snížením [[Daň|daní]]{{done}} nebo vládními převodními platbami, a výsledný deficit je financován půjčkami z centrální banky nebo prostým tisknutím peněz”.<ref>{{Citace periodika | příjmení = Haberler | jméno = Gottfried | titul = The Pigou Effect Once More | periodikum = Journal of Political Economy | datum vydání = 1952-06-01 | ročník = 60 | číslo = 3 | strany = 240–246 | issn = 0022-3808 | doi = 10.1086/257211 | url = https://www.journals.uchicago.edu/doi/10.1086/257211 | datum přístupu = 2019-11-29 }}</ref> Je pozoruhodné, jak v kontext nedávných debat o rozdělení mezi měnové a [[Fiskální politika|fiskální politiky]]{{done}} viděl Friedman tyto politiky jako důkaz o síle měnové politiky. Na stejné AER adrese je vysoce kritický vůči včasnosti a účinnosti fiskálních opatření ke stabilizaci poptávky. Myšlenka helikoptérového poklesu byla obnovena jako vážný politický návrh okolo roku 2000, kdy se ekonomové chtěli nechat inspirovat od Japonska. Ben Bernanke, guvernér federální rezervní rady, přednesl proslov v listopadu 2002 na téma jak předejít deflaci, kde řekl, že Keynes” jednou skoro vážně navrhnul jako anti-deflační opatření to, že by vláda naplnila lahve s měnou, které by pohřbila je do dolních šacht, a posléze by byla vykopána veřejností.” V tomto proslovu Bernanke sám řekl, „snížení daní financované z peněz je v podstatě rovnocenné vyhození peněz z helikoptéry od Miltona Friedmana.” <ref>{{Citace elektronického periodika | titul = Speech, Bernanke --Deflation-- November 21, 2002 | periodikum = www.federalreserve.gov | url = https://www.federalreserve.gov/boarddocs/Speeches/2002/20021121/default.htm#f8 | datum přístupu = 2019-11-29 }}</ref> V rámci jeho proslovu Bernanke také odkazuje na důležitý dokument Gauti Eggertsona, který zdůrazňuje význam závazku centrálních bank udržet si v budoucnu zásobu peněz.<ref>{{Citace elektronického periodika | titul = How to Fight Deflation in a Liquidity Trap : Committing to Being Irresponsible : IMF Working Paper: How to Fight Deflation in a Liquidity Trap - Committing to Being Irresponsible: | periodikum = www.elibrary.imf.org | url = https://www.elibrary.imf.org/view/IMF001/03171-9781451848588/03171-9781451848588/03171-9781451848588.xml | datum vydání = 2003-03-01 | doi = 10.5089/9781451848588.001 | jazyk = en | datum přístupu = 2019-11-29 }}</ref> Irský ekonom Eric Lonergan také tvrdil v roce 2002 v [[Financial Times|Financial Times]]{{done}}, že centrální banky považují převody peněz do domácností jako alternativu pro další snižování úrokových sazeb a také tlaku na [[Finanční stabilita|finanční stabilitu]].<ref>Beyond interest rates, Financial Times, Sep 09, 2002</ref> V roce 2003, Willem Buiter, poté vedoucí ekonom v Evropské bance pro Rekonstrukci a vývoj, obnovil koncept Helikoptérových peněz v teoretickém dokumentu, kde tvrdil, že peněžní základ není závazek, což poskytuje přísnější argument pro Friedmanovy a Haberlerovy Pigouvianovy intuice. <ref>{{Citace monografie | příjmení = Buiter | jméno = Willem H. | titul = Helicopter Money: Irredeemable Fiat Money and the Liquidity Trap | url = https://ideas.repec.org/p/cpr/ceprdp/4202.html | jazyk = en }}</ref> Od roku 2012, ekonomové nazývali tuto představu jako “kvantitativní uvolňování pro lidi.”<ref name=":0">{{Citace elektronické monografie | příjmení = Kaletsky | jméno = Anatole | titul = Reuters Blogs | url = http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ | datum vydání = 2012-08-01 | datum přístupu = 2019-11-29 }} {{Webarchive|url=https://web.archive.org/web/20130528204341/http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |date=2013-05-28 }} {{Cite web |url=http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |title=Archived copy |access-date=2020-02-17 |archive-date=2013-05-28 |archive-url=https://web.archive.org/web/20130528204341/http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |url-status=dead }}{{Cite web |url=http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |title=Archived copy |access-date=2020-02-17 |archive-date=2013-05-28 |archive-url=https://web.archive.org/web/20130528204341/http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |url-status=dead }} {{Cite web |url=http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |title=Archived copy |access-date=2020-02-17 |archive-date=2013-05-28 |archive-url=https://web.archive.org/web/20130528204341/http://blogs.reuters.com/anatole-kaletsky/2012/08/01/how-about-quantitative-easing-for-the-people/ |url-status=dead }}</ref><ref name=":1">{{Citace elektronické monografie | příjmení = Muellbauer | jméno = John | titul = VoxEU.org | url = https://voxeu.org/article/combatting-eurozone-deflation-qe-people | datum vydání = 2014-12-23 | datum přístupu = 2019-11-29 }}</ref> == Politická reakce na světovou finanční krizi == V [[Prosinec 2008|prosince 2008]]{{done}}, Eric Lonergan a [[Martin Wolf|Martin Wolf]]{{done}} navrhli v Financial Times, že by centrální banky prováděli převody peněz přímo domácnostem, aby bojovali s hrozbou světové deflace.<ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = Central banks may soon resort to their most powerful weapons against deflation: the printing press and the "helicopter drop" of money | periodikum = | vydavatel = | url = https://www.ft.com/cms/s/0/d049482c-cb8f-11dd-ba02-000077b07658.html#axzz45kZzwC6m | datum vydání = | url archivu = | datum přístupu = 2019-11-29 }}</ref><ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = The most direct and efficient solution to the economic and financial problems is for central banks to transfer cash directly to the household sector | periodikum = | vydavatel = | url = http://blogs.ft.com/economistsforum/2008/12/central-banks-need-a-helicopter/ | datum vydání = | url archivu = | datum přístupu = 2019-11-29 }}</ref> Okolo roku 2012 někteří ekonomové začali zastávat možný helikoptérový pokles, včetně QE, a možné dluhové jubileum financované monetárním základem.<ref name=":0" /> Tyto návrhy odráželi pocit, že tradiční politiky, včetně QE, selhávaly nebo měly mnoho nepříznivých vlivů - na finanční stabilitu nebo distribuci bohatství a příjmu. V roce 2013, předseda britského úřadu pro finanční službu (FCA), Adair Turner, který byl považován za možného nástupce Mervyna Kinga, guvernéra banky Anglie, tvrdil, že deficit zpeněžení je nejrychlejší cesta, jak se vzpamatovat z [[Velká recese|finanční krize]]{{done}}.<ref>{{Citace elektronického periodika | titul = Debt, Money and Mephistopheles: How do we get out of this mess? | periodikum = FCA | url = https://www.fca.org.uk/news/speeches/debt-money-and-mephistopheles-how-do-we-get-out-mess | datum vydání = 2013-02-06 | jazyk = en | datum přístupu = 2019-11-29 }}</ref> == Realizace == Přestože původní definice helikoptérových peněz popisuje situaci, kde centrální banky distribuují  peníze přímo jednotlivcům, více moderní užití tohoto pojmu odkazuje na další možnosti jako třeba na poskytování univerzálních daňových slev všem domácnostem, financované centrální bankou. Toto bylo například aplikováno v Austrálii v roce 2009<ref>{{Citace periodika | příjmení = Leigh | jméno = Andrew | titul = How Much Did the 2009 Australian Fiscal Stimulus Boost Demand? Evidence from Household-Reported Spending Effects | periodikum = The B.E. Journal of Macroeconomics | datum vydání = 2012 | ročník = 12 | číslo = 1 | issn = 1935-1690 | doi = 10.1515/1935-1690.2035 | url = https://www.degruyter.com/view/j/bejm.2012.12.issue-1/1935-1690.2035/1935-1690.2035.xml | datum přístupu = 2019-11-29 }}</ref> nebo v USA jako Economic Stimulus Act (Ekonomický stimulační akt) v roce 2008. Využití slev na daní vysvětluje, proč někteří považují helikoptérové peníze za fiskální impuls a ne za nástroj měnové politiky. Nicméně helikoptérové peníze nejsou zamýšleny jako trvalý koncept, a proto jsou jinou politickou možností než všeobecným základním příjemem.<ref>{{Citace elektronické monografie | titul = Helicopter money and basic income: friends or foes? {{!}} BIEN | url = https://basicincome.org/news/2017/03/helicopter-money-basic-income-friends-or-foes/ | datum přístupu = 2019-11-29 | jazyk = en-US }} {{Webarchive|url=https://web.archive.org/web/20170407135327/http://basicincome.org/news/2017/03/helicopter-money-basic-income-friends-or-foes/ |date=2017-04-07 }}</ref> Pod přísnou definicí, kde helikoptérové poklesy jsou prosté převody z centrální banky do privátního sektoru financovaného základními penězi. Někteří ekonomové tvrdí, že se tato situace v některých situacích zrealizovala.<ref>{{Citace periodika | příjmení = Khan | jméno = Mehreen | titul = Central banks are already doing the unthinkable - you just don't know it | periodikum = The Telegraph | datum vydání = 2016-03-19 | issn = 0307-1235 | jazyk = en-GB | url = https://www.telegraph.co.uk/business/2016/03/17/central-banks-are-already-doing-the-unthinkable---you-just-dont/ | datum přístupu = 2019-11-29 }}</ref> V roce 2016, [[Evropská centrální banka|Evropská centrální banka]]{{done}} zavedla TLTRO program, který spočívá v půjčování peněz bankám za záporné úrokové sazby, což představuje obnos, který je převáděn do bank. Také používání jiných úrokových sazeb na řadu rezerv vede k situaci, kdy komerční banky budou čelit záporným úrokovým sazbám. A to otevírá další zdroj helikoptérového poklesu - třebaže banky dělají prostředníka.<ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = Central banks have ways to drop money into ailing economies | periodikum = | vydavatel = | url = https://www.ft.com/cms/s/3/914fae3e-dec6-11e5-b072-006d8d362ba3.html#axzz45kZzwC6m | datum vydání = | url archivu = | datum přístupu = 2019-11-29 }}</ref> V případě použití TLTRO v eurozóně by podle některých ekonomů mělo poskytnout legální a administrativně dohledatelné prostředky k zavedení převodů do domácností.<ref>{{Citace elektronické monografie | titul = Helicopter drops reloaded {{!}} Bruegel | url = https://bruegel.org/2016/03/helicopter-drops-reloaded/ | datum přístupu = 2019-11-29 | jazyk = en-US }}</ref> Ekonomik Eric Lonergan argumentoval v roce 2016, že legální helikoptérový pokles v eurozóně by mohl být strukturovaný přes nulový kupon (neustálé půjčky), na který by všichni evropští dospělý občané měli nárok. Půjčky by mohly spravovat způsobilé komerční banky a za určitých podmínek by půjčky byly čistým úrokovým výnosem ve prospěch centrálních bank.<ref>{{Citace elektronické monografie | titul = Philosophy of Money | url = https://www.philosophyofmoney.net/legal-helicopter-drops-in-the-eurozone/ | datum vydání = 2016-02-24 | datum přístupu = 2019-11-29 | jazyk = en-GB }}</ref> Tuto myšlenku oživila v roce 2019 Frances Coppola v její knize The Case for People’s Quantitative Easing (Případ pro kvantitativní uvolnění lidí) a také vědci v Blackrocku, včetně Stanleyho Fischera, a také francouzského ekonoma Jeana Pisani-Ferryho. == Kontroverze == Mnoho ekonomů je toho názoru, že helikoptérové peníze ve formě převodů hotovosti do domácností by neměly být považovány za náhražku fiskální politiky. Vzhledem k tomu, že vládní náklady na vypůjčení jsou extrémně nízké a téměř nulové úrokové sazby, měly by fungovat fiskální pobídky prostřednictvím snížení daní a výdajů na infrastrukturu. Z tohoto hlediska jsou helikoptérové peníze skutečně pojistkou proti selhání fiskální politiky z politických, právnických nebo institucionálních důvodů.<ref>{{Citace elektronické monografie | příjmení = Macro | jméno = Mainly | titul = mainly macro | url = https://mainlymacro.blogspot.com/2016/03/two-related-confusions-about-helicopter.html | datum vydání = 2016-03-01 | datum přístupu = 2019-11-29 }}</ref> === Odlišnosti od kvantitativního uvolňování === Stejně jako všechny expanzivní měnové politiky i kvantitativní uvolňování (QE) a helikoptérové peníze zahrnují výrobu peněz centrálními bankami za účelem šíření peněz. Nicméně dopad na rovnováhu rozvah (bank balance sheet) centrálních bank helikoptérovými penězi se liší od kvantitativním uvolňováním. Pod QE si centrální banky vytvářejí rezervy tím, že nakupují dluhopisy nebo jiné finanční aktiva, provádějí tzv. Asset swap (výměna aktiv). Naproti tomu s helikoptérovými penězi, centrální banky rozdávají vytvořené peníze bez toho aniž by navyšovali aktiva na jejich rozvahách. Ekonomové tvrdí, že účinek je od očekávání jiný, protože vytvořené helikoptérové peníze by byly vnímány jako trvalé - což je více nezvratné než kvantitativní uvolňování. Ekonomové poukazují na to, že efekt není příliš odlišný od kombinace expanzivní měnové politiky a expanzivní fiskální politiky. === Implikace pro rozvahy centrálních bank === Jednou z hlavních obav při převodech z centrálních bank přímo do soukromého sektoru je, že na rozdíl od běžných operací na [[Volný trh|volném trhu]]{{done}} nemá centrální banka aktivum odpovídající vytvořeným základním penězům. Pro měřený kapitál centrální banky z toho plynou důsledky, protože základní peníze se obvykle považují za závazek, ale mohlo by to také omezit schopnost centrální banky stanovit úrokové sazby v budoucnu. Účetní zacházení centrálních bank s rozvahami je poměrně kontroverzní.<ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = Governments can borrow without increasing their debt | periodikum = | vydavatel = | url = http://blogs.ft.com/economistsforum/2012/06/governments-can-borrow-without-increasing-their-debt/ | datum vydání = | url archivu = | datum přístupu = 2019-11-30 }}</ref> Většina ekonomů je toho názoru, že kapitál centrálních bank není příliš důležitý.<ref>{{Citace periodika | příjmení = European Central Bank (Frankfurt | jméno = Main) | titul = Working paper series | periodikum = Working paper series | datum vydání = 1999 | issn = 1725-2806 | poznámka = OCLC: 890617800 | jazyk = English | url = https://www.worldcat.org/title/working-paper-series/oclc/890617800 | datum přístupu = 2019-11-29 }}</ref> To na čem opravdu záleží je, zda může být v budoucnu expanze základních peněz zvrácena nebo zda se jedná o jejich další prostředky ke zvýšení úrokové sazby. Bylo navrženo mnoho řešení. Oxfordský profesor, Simon Wren- Lewis navrhl, aby se vláda předběžně zavázala poskytovat dluhopisy v případě potřeby banky Anglie. Evropská centrální banka může nařídit navýšení svého kapitálu a zavedení odstupňovaných rezerv, a z úroků z rezerv poskytuje centrálním bankám řadu nástrojů na ochranu jejich vlastního čistého příjmu a poptávky po rezervách. <ref>{{Citace elektronické monografie | příjmení = Lonergan | jméno = Eric | titul = Philosophy of Money | url = https://www.philosophyofmoney.net/does-the-central-banks-balance-sheet-matter/ | datum vydání = 2015-05-25 | datum přístupu = 2019-11-29 | jazyk = en-GB }}</ref> == Příznivci == Bývalý předseda [[Federální rezervní systém|federální rezervní banky]]{{done}} Ben Bernanke je jedním ze známých zastánců helikoprérových peněz. V listopadu roku 2002 ve svém projevu o Japonsku pronesl, že státem financované daňové úlevy jsou ve své podstatě ekvivalent Friedmanova známého konceptu helikoptérových peněz<ref>{{Citace elektronického periodika | titul = Speech, Bernanke --Deflation-- November 21, 2002 | periodikum = www.federalreserve.gov | url = https://www.federalreserve.gov/boarddocs/Speeches/2002/20021121/default.htm | datum přístupu = 2019-11-29 }}</ref>. V dubnu roku 2016 Ben Bernanke napsal článek ve kterém uvedl, že takové plány jsou tou nejlepší možnou alternativou a bylo by nerozumné je ignorovat.<ref>{{Citace elektronické monografie | příjmení = Bernanke | jméno = Ben S. | titul = Brookings | url = https://www.brookings.edu/blog/ben-bernanke/2016/04/11/what-tools-does-the-fed-have-left-part-3-helicopter-money/ | datum vydání = 2016-04-11 | datum přístupu = 2019-11-29 | jazyk = en-US }}</ref> Federální předsedkyně [[Janet Yellenová|Janet Yellen]]{{done}} také uznala, že helikoptérové peníze by mohly představovat možnost v extrémních případech.<ref>{{Citace elektronického periodika | příjmení = Gillespie | jméno = Patrick | titul = Janet Yellen: Helicopter money is an option in extreme situations | periodikum = CNNMoney | url = https://money.cnn.com/2016/06/16/news/economy/federal-reserve-janet-yellen-helicopter-money/index.html | datum vydání = 2016-06-16 | datum přístupu = 2019-11-29 }}</ref> Federální ředitel a ekonom Willem Buiter je také prominentním advokátem tohoto konceptu.<ref>{{Citace periodika | příjmení = Seith | jméno = Anne | titul = Operation Helicopter: Could Free Money Help the Euro Zone? | periodikum = Spiegel Online | datum vydání = 2015-01-06 | url = https://www.spiegel.de/international/business/economists-say-handing-out-cash-could-help-euro-zone-economy-a-1011352.html | datum přístupu = 2019-11-29 }}</ref> Mezi jiné příznivce patří Martin Wolf<ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = The case for helicopter money | periodikum = | vydavatel = | url = https://www.ft.com/cms/s/0/9bcf0eea-6f98-11e2-b906-00144feab49a.html#axzz2vaE3myVK | datum vydání = | url archivu = | datum přístupu = 2019-11-30 }}</ref>, oxfordský ekonom John Muellbauer<ref name=":1" />,  Simon Wren-Lewis, ekonom Steve Keen, ekonom Mark Blyth z Brown University, profesor ekonomie a bývalý pokladní poradce Brad DeLong<ref>{{Citace elektronického periodika | příjmení = DeLong | jméno = J. Bradford | titul = Rescue Helicopters for Stranded Economies {{!}} by J. Bradford DeLong | periodikum = Project Syndicate | url = https://www.project-syndicate.org/commentary/helicopter-money-fiscal-stimulus-by-j--bradford-delong-2016-04 | datum vydání = 2016-04-29 | jazyk = en | datum přístupu = 2019-11-29 }}</ref><ref>{{Citace elektronického periodika | příjmení = Delong | jméno = J. Bradford | titul = Helicopter Money: When Zero Just Isn’t Low Enough | periodikum = Milken Institute Review | url = http://www.milkenreview.org/articles/helicopter-money-when-zero-just-isnt-low-enough | jazyk = en-US | datum přístupu = 2019-11-29 }}</ref>, UCLA profesor ekonomie Roger Farmer, Ray Dalio, irský ekonom a pokladní manažer Eric Lonergan<ref>{{Citace monografie | titul = Why the ECB should give money directly to People (Eric Lonergan) | url = https://www.youtube.com/watch?v=5EA2nKqcjtg | jazyk = cs-CZ }}</ref>, Anatole Kaletsky<ref>{{Citace elektronického periodika | příjmení = Kaletsky | jméno = Anatole | titul = Central Banking’s Final Frontier? {{!}} by Anatole Kaletsky | periodikum = Project Syndicate | url = https://www.project-syndicate.org/onpoint/central-bankings-final-frontier-by-anatole-kaletsky-2016-05 | datum vydání = 2016-05-06 | jazyk = en | datum přístupu = 2019-11-29 }}</ref>, Romain Baeriswyl<ref>{{Citace monografie | příjmení = Baeriswyl | jméno = Romain | titul = The Case for the Separation of Money and Credit | url = https://doi.org/10.1007/978-3-319-56261-2_6 | editoři = Frank Heinemann, Ulrich Klüh, Sebastian Watzka | vydavatel = Springer International Publishing | místo = Cham | strany = 105–121 | isbn = 978-3-319-56261-2 | doi = 10.1007/978-3-319-56261-2_6 | poznámka = DOI: 10.1007/978-3-319-56261-2_6 | jazyk = en }}</ref>, Martin Sandbu<ref>{{Citace elektronického periodika | příjmení = | jméno = | titul = Helicopter money: if not now, then when? | periodikum = | vydavatel = | url = https://www.ft.com/content/1dc5b4a8-57da-11e6-9f70-badea1b336d4 | datum vydání = | url archivu = | datum přístupu = 2019-11-30 }}</ref>, Jean Pisani-Ferry<ref>{{Citace elektronického periodika | příjmení = Pisani-Ferry | jméno = Jean | titul = How to Ward Off the Next Recession {{!}} by Jean Pisani-Ferry | periodikum = Project Syndicate | url = https://www.project-syndicate.org/commentary/europe-next-recession-alternative-policies-by-jean-pisani-ferry-2019-09 | datum vydání = 2019-09-30 | jazyk = en | datum přístupu = 2019-11-29 }}</ref>. Myšlenka helikoptérových peněz má své podporovatele také mezi guvernéry centrálních bank. Například bývalý předseda irské centrální banky Patrick Honohan uvedl, že věří že by [[Zakázané moře|tato politika]]{{not done}}, unrelated mohla fungovat. Vrchní ekonom ECB Peter Praet jednou řekl, že by se tímto mohly řídit všechny centrální banky. Místoguvernér České centrální banky [[Mojmír Hampl|Mojmír Hampl]]{{done}} publikoval práci, ve které napsal že tato myšlenka nabízí mnoho výhod v porovnání s jinými formami nekonvenčních měnových politik, zejména eliminuje potřebu spoléhat se na komplikovaný přenosný mechanismus, což umožňuje mnohem snazší komunikaci s veřejností a posílení důvěry spotřebitelů, když je to nejvíce potřeba.<ref>{{Citace periodika | příjmení = Hampl | jméno = Mojmir | příjmení2 = Havranek | jméno2 = Tomas | titul = Central Bank Capital as an Instrument of Monetary Policy | datum vydání = 2018 | url = https://www.econstor.eu/handle/10419/176828 | datum přístupu = 2019-11-29 }}</ref> Bill Gross, manažer portfolia Globálního dluhopisového fondu Janus Global Unconstrained Bond Fund, také podporuje implementaci základního příjmu v podobě helikoptérových peněz.<ref>{{Citace elektronické monografie | příjmení = Vigna | jméno = Paul | titul = WSJ | url = https://blogs.wsj.com/moneybeat/2016/05/04/bill-gross-what-to-do-after-the-robots-take-our-jobs/ | datum vydání = 2016-05-04 | datum přístupu = 2019-11-29 | jazyk = en-US }}</ref>V srpnu 2019 prominentní centrální bankéři [[Stanley Fischer|Stanley Fischer]] a Philip Hildebrand napsali dokument vydaný společností BlackRock<ref>{{Citace elektronického periodika | titul = How central banks might deal with the next downturn | periodikum = BlackRock Blog | url = https://www.blackrockblog.com/2019/08/20/how-central-banks-might-deal-with-the-next-downturn/ | datum vydání = 2019-08-20 | jazyk = en-US | datum přístupu = 2019-11-29 }}</ref>, v němž navrhují formu helikoptérových peněz. == Kritika == === Inflační efekt === V minulosti byl nápad helikoptérových peněz zamítnut, protože se předpokládalo, že nevyhnutelně povede k hyperinflaci. V důsledku toho vyplula na povrch řada obav a to hlavně, že helikoptérové peníze by narušily důvěru v měnu (což by nakonec vedlo k hyperinflaci). Toto znepokojení vyslovil zejména německý ekonom (a bývalý hlavní ekonom v ECB) Otmar Issing v příspěvku z roku 2014.<ref>{{Citace elektronického periodika | titul = Die letzte Waffe - Helicopter Money? - boersen-zeitung.de | periodikum = www.boersen-zeitung.de | url = https://www.boersen-zeitung.de/index.php?li=1&artid=2015026026 | jazyk = de | datum přístupu = 2019-11-29 }} {{Cite web |title=Archived copy |url=https://www.boersen-zeitung.de/index.php?li=1&artid=2015026026 |access-date=2020-02-17 |archive-date=2020-06-11 |archive-url=https://web.archive.org/web/20200611172923/https://www.boersen-zeitung.de/index.php?li=1&artid=2015026026 }}</ref> Později v roce 2016 v rozhovoru prohlásil: „Myslím, že celá myšlenka na helikoptérové peníze je naprosto devastující. To není nic jiného než prohlášení o bankrotu měnové politiky.“<ref>{{Citace periodika | titul = Ex-EZB-Chefvolkswirt Issing: „Helikoptergeld“ wäre Bankrotterklärung | issn = 0174-4909 | jazyk = de | url = https://www.faz.net/1.4141309 | datum přístupu = 2019-11-29 }}</ref> Richard Koo uvádí podobný argument<ref>{{Citace elektronického periodika | periodikum = ftalphaville.ft.com | url = https://ftalphaville.ft.com/2016/07/27/2170980/koo-on-why-helicopter-money-just-wont-work/ | datum přístupu = 2019-11-29 }}</ref> , řekl: „pokud by takové obálky dorazily den co den, celá země by rychle upadla do paniky, protože lidé se nevyznají jakou hodnotu tedy jejich měna má.“ To je v rozporu s argumentem, že lidé nebudou utrácet hodně peněz, které dostanou (a proto helikoptérové nemohou být inflační). === Mohly by být helikoptérové peníze utraceny? === Několik prominentních ekonomů, jako je centrální bankéř Raghuram Rajan, jsou proti helikoptérovým penězům z důvodu, že by nebyly účinné, protože lidé by peníze neutratili.<ref>{{Citace periodika | příjmení = Cassidy | jméno = John | titul = Raghuram Rajan and the Dangers of Helicopter Money | datum vydání = 2016-05-13 | issn = 0028-792X | jazyk = en | url = https://www.newyorker.com/news/john-cassidy/raghuram-rajan-and-the-dangers-of-helicopter-money | datum přístupu = 2019-11-29 }}</ref> Lord Adair Turner v reakci na to tvrdí: „Deficity financované penězi budou vždy stimulovat nominální poptávku. Ve srovnání s tím deficity zadlužení mohou udělat, ale nemusí.“<ref>{{Citace elektronického periodika | titul = Why a future tax on bank credit intermediation does not offset the stimulative effect of money finance deficits | periodikum = Institute for New Economic Thinking | url = https://www.ineteconomics.org/research/research-papers/why-a-future-tax-on-bank-credit-intermediation-does-not-offset-the-stimulative-effect-of-money-finance-deficits | jazyk = en | datum přístupu = 2019-11-29 }}</ref> === „Neexistuje nic jako oběd zdarma“ === Další řada kritiků přichází s myšlenkou, že nemůže existovat něco jako „volné peníze“ nebo jak ekonomové říkají „neexistuje nic jako oběd zdarma“. Tuto kritiku vyjádřila zejména skupina vědců pro mezinárodní vypořádání Claudio Borio, Piti Disyatat, Anna Zabai.<ref>{{Citace elektronického periodika | titul = 538 - Will helicopter money be spent? New evidence - De Nederlandsche Bank | periodikum = www.dnb.nl | url = https://www.dnb.nl/en/news/dnb-publications/dnb-working-papers-series/dnb-working-papers/working-papers-2016/dnb350283.jsp | datum přístupu = 2019-11-29 }} {{Cite web |title=Archived copy |url=https://www.dnb.nl/en/news/dnb-publications/dnb-working-papers-series/dnb-working-papers/working-papers-2016/dnb350283.jsp |access-date=2020-02-17 |archive-date=2020-02-17 |archive-url=https://web.archive.org/web/20200217130321/https://www.dnb.nl/en/news/dnb-publications/dnb-working-papers-series/dnb-working-papers/working-papers-2016/dnb350283.jsp }}</ref> , která tvrdila, že helikoptérový pokles by pro občany nutně vyžadoval, aby centrální banka platila úroky z dodávaných zvláštních rezerv. V reakci na to bývalý ekonom MMF Biagio Bossone zpochybňuje pozdější předpoklad<ref>{{Citace elektronického periodika | titul = Why Helicopter Money is a “Free Lunch” {{!}} EconoMonitor | periodikum = archive.economonitor.com | url = http://archive.economonitor.com/blog/2016/06/why-helicopter-money-is-a-free-lunch/ | datum přístupu = 2019-11-29 }} {{Webarchive|url=https://web.archive.org/web/20191105143401/http://archive.economonitor.com/blog/2016/06/why-helicopter-money-is-a-free-lunch/ |date=2019-11-05 }} {{Cite web |title=Archived copy |url=http://archive.economonitor.com/blog/2016/06/why-helicopter-money-is-a-free-lunch/ |access-date=2020-02-17 |archive-date=2019-11-05 |archive-url=https://web.archive.org/web/20191105143401/http://archive.economonitor.com/blog/2016/06/why-helicopter-money-is-a-free-lunch/ }}</ref> a tvrdí, že „helikoptérové peníze jsou„ oběd zdarma “ nebo-li, že pokud to funguje a podaří se mu překonat mezeru výstupu, lidé ji nebudou muset splatit vyššími daněmi nebo nežádoucí (nad optimální) inflací. === Zákonnost === Jiní kritici tvrdí, že helikoptérové peníze by byly mimo mandát centrálních bank, protože by to „setřelo hranice mezi fiskální politikou a měnovou politikou“<ref>{{Citace elektronického periodika | příjmení = Bank | jméno = European Central | titul = Interview with Süddeutsche Zeitung | periodikum = European Central Bank | url = https://www.ecb.europa.eu/press/inter/date/2016/html/sp160926_3.en.html | jazyk = en | datum přístupu = 2019-11-29 }}</ref> převážně z toho důvodu, že helikoptérové peníze by zahrnovaly „fiskální účinky“, což je tradičně úloha vlád. Zastáncové helikoptérových peněz, jako jsou Eric Lonergan a Simon Wren-Lewis, však vyvracejí tento argument tím, že standardní nástroje měnové politiky mají také fiskální dopady.<ref>{{Citace elektronické monografie | příjmení = Macro | jméno = Mainly | titul = mainly macro | url = https://mainlymacro.blogspot.com/2016/05/helicopter-money-and-fiscal-policy.html | datum vydání = 2016-05-20 | datum přístupu = 2019-11-29 }}</ref><ref>{{Citace elektronické monografie | titul = Philosophy of Money | url = https://www.philosophyofmoney.net/the-distinction-between-monetary-and-fiscal-policy/ | datum vydání = 2016-04-03 | datum přístupu = 2019-11-29 | jazyk = en-GB }}</ref> Evropská centrální banka v dopise europoslanci Jonásovi Fernándezovi<ref>{{Citace elektronického periodika | příjmení = Jourdan | jméno = Stan | titul = ECB confirms 'helicopter money' is legally feasible under conditions | periodikum = Positive Money Europe | url = https://www.positivemoney.eu/2016/12/ecb-confirms-helicopter-money-legally-feasible/ | datum vydání = 2016-12-07 | jazyk = en-GB | datum přístupu = 2019-11-29 }}</ref> vysvětlila, že „právní složitost by stále mohla nastat, pokud by se na tento systém mohlo nahlížet jako na ECB, která financuje závazek [[Veřejná správa|veřejného sektoru]] {{done}}vůči třetím stranám, protože by to porušilo také zákaz měnového financování “. Jedná se však o velmi nepravděpodobný a nežádoucí případ, kdy by výplaty helikoptérových peněz centrální bankou nahrazovaly platby za sociální zabezpečení (které jsou odpovědností vlád). === Účetní problémy === Prezident Bundesbank Jens Weidmann také vyjádřil opozici proti helikoptérovým penězům a tvrdil, že by to „roztrhlo mezery v rozvahách centrálních bank. Nakonec by náklady nesly země [[Eurozóna|eurozóny]]{{done}}, a tedy daňoví poplatníci, protože by centrální banky po nějakou dobu neprofitovaly. “<ref>{{Citace elektronického periodika | titul = Deutsche Bundesbank - Interviews - "Helicopter money would tear gaping holes in central bank balance sheets" | periodikum = web.archive.org | url = https://web.archive.org/web/20160518205952/http://www.bundesbank.de/Redaktion/EN/Interviews/2016_01_29_weidmann_funke_mediengruppe.html | datum vydání = 2016-05-18 | datum přístupu = 2019-11-29 }}</ref> Belgická národní banka také vydala dokument, který uvádí podobný argument.<ref>{{Citace elektronického periodika | titul = Helicopter money and debt-financed fiscal stimulus: one and the same thing? {{!}} nbb.be | periodikum = www.nbb.be | url = https://www.nbb.be/en/articles/helicopter-money-and-debt-financed-fiscal-stimulus-one-and-same-thing | datum přístupu = 2019-11-29 }}</ref> == V eurozóně == Od 10. března 2016 se tento nápad v Evropě stává stále populárnějším poté, co [[Mario Draghi|Mario Draghi]]{{done}}, prezident Evropské centrální banky, na [[Tisková konference|tiskové konferenci]]{{done}} uvedl, že tento koncept považuje za „velmi zajímavý“. [59] Po tomto prohlášení následovalo další prohlášení ECB Petera Praeta, který prohlásil: [60] „Ano, všechny centrální banky to dokážou. Můžete vydat měnu a rozdělit ji lidem. To jsou helikoptérové peníze. Helikoptérové peníze dávají lidem část [[Čistá současná hodnota|čisté současné hodnoty]]{{done}} vaší budoucí seigniorage, zisk, který vyděláte v budoucnosti. Otázka je, zda a kdy je vhodné použít nástroj, který je opravdu extrémním nástrojem. ““ V roce 2015 byla zahájena evropská kampaň nazvaná „Kvantitativní uvolňování pro lidi“<ref>{{Citace elektronického periodika | titul = Handtekeningenactie economen tegen ECB | periodikum = fd.nl | url = https://fd.nl/economie-politiek/1129670/handtekeningenactie-voor-helikoptergeld | datum přístupu = 2019-11-30 }}</ref>, která účinně propaguje koncept helikoptérových peněz, spolu s dalšími návrhy na „zelené kvantitativní uvolňování“ a „strategické kvantitativní uvolňování“, které jsou dalšími typy operací měnového financování centrálními bankami zapojujícími programy veřejných investic. Kampaň v současné době podporuje 20 organizací v celé Evropě a více než 116 ekonomů.<ref>{{Citace elektronického periodika | titul = Positive Money Europe | periodikum = Positive Money Europe | url = https://www.positivemoney.eu/ | jazyk = en-GB | datum přístupu = 2019-11-30 }}</ref> Dne 17. června 2016 podepsalo 18 poslanců Evropského parlamentu (včetně Philippa Lambertse, Paula Tanga a Fabia De Masiho) otevřený dopis<ref>{{Citace elektronického periodika | titul = Positive Money Europe | periodikum = Positive Money Europe | url = https://www.positivemoney.eu/ | jazyk = en-GB | datum přístupu = 2019-11-30 }}</ref> , v němž vyzívá Evropská Centrální Banka, aby „poskytla důkazy podloženou analýzu potenciálních účinků alternativních návrhů  uvedené výše a aby objasnili, za jakých podmínek by bylo jejich provedení legální. “ Pokud neuvažuje o alternativách k QE, europoslanci se obávají, že by se Evropská Centrální Banka „nepřipravila na zhoršení ekonomických podmínek“<ref>{{Citace elektronického periodika | titul = Subscribe to read | periodikum = Financial Times | url = https://www.ft.com/content/c5d08c5c-339c-11e6-bda0-04585c31b153 | jazyk = en-GB | datum přístupu = 2019-11-30 }}</ref>. V říjnu 2016 průzkum ukázal, že 54% Evropanů si myslí, že helikoptérové peníze by byly dobrým nápadem, pouze 14% bylo proti.<ref>{{Citace elektronického periodika | titul = Positive Money Europe | periodikum = Positive Money Europe | url = https://www.positivemoney.eu/ | jazyk = en-GB | datum přístupu = 2019-11-30 }}</ref> == V Japonsku == Na setkání s japonským premiérem Shinzo Abe a japonskou bankou Haruhiko Kuroda v červenci 2016<ref name=":2">{{Citace elektronického periodika | titul = Can 'Helicopter Ben Bernanke' Save Japan? | periodikum = The Fiscal Times | url = http://www.thefiscaltimes.com/Columns/2016/07/12/Can-Helicopter-Ben-Bernanke-Save-Japan | jazyk = en | datum přístupu = 2019-11-30 }}</ref> bylo prohlášeno<ref name=":2" />, že bývalý předseda federální rezervy Ben Bernanke doporučoval politiku zpeněžování většího vládního dluhu vytvořeného na financování projektů v oblasti [[Infrastruktura|infrastruktury]]{{done}},<ref>{{Citace elektronického periodika | titul = Bloomberg - Are you a robot? | periodikum = www.bloomberg.com | url = https://www.bloomberg.com/tosv2.html?vid=&uuid=08ad97e0-1305-11ea-ae53-f972e876bd5e&url=L25ld3MvYXJ0aWNsZXMvMjAxNi0wNy0yNy9hYmUtcy11bnVzdWFsLXN0aW11bHVzLWFubm91bmNlbWVudC1tYXktYmUtZGlyZWN0ZWQtYXQtdGhlLWJvag== | datum přístupu = 2019-11-30 }}</ref> jako způsob, jak upustit „helikoptérové peníze" na Japonsko, aby stimuloval ekonomiku a zastavil deflaci v Japonsku. Finanční trhy začaly tuto iniciativu stimulovat dopředu, než byly poprvé ohlášeny Bernankeho návštěvy Japonska. O měsíc později bylo prohlášeno, že Japonská centrální banka, která právě provádí přezkum svého měnového stimulačního programu, zvažuje politiky poněkud podobné „helikoptérovým penězům“, jako je prodej 50 letých nebo věčných dluhopisů.<ref>{{Citace periodika | příjmení = White | jméno = Stanley | titul = 'Helicopter money' talk takes flight as Bank of Japan runs out of runway | periodikum = The Japan Times Online | datum vydání = 2016-07-31 | issn = 0447-5763 | jazyk = en-US | url = https://www.japantimes.co.jp/news/2016/07/31/business/economy-business/helicopter-money-talk-takes-flight-bank-japan-runs-runway/ | datum přístupu = 2019-11-30 }}</ref> == Reference == <references /> {{Portály|Ekonomie}} [[Kategorie:Měnová politika]] c9qbctqks0r5cxv6ifmp0tfhk00ui5y EntitySchema:201 0 109233 739869 425290 2026-04-30T11:12:53Z ~2026-26374-42 73763 739869 wikitext text/x-wiki {{Under review|date=April 2020}} test edit testcaptcha f2hsa6r9gee2u9070cxjstii722lkja 739870 739869 2026-04-30T11:13:26Z ~2026-26374-42 73763 /* */ tests 739870 wikitext text/x-wiki {{Under review|date=April 2020}} test edit testcaptcha teststestsets l7q0pskiby3nhbmvnolxdpa110qc5p0 ApiTestEdit2 0 109983 739871 685342 2026-04-30T11:22:53Z Test-mpa 73113 739871 wikitext text/x-wiki ����͓��e�̃e�X�g�ł��B�e�X�g�ԍ���2 this is an edit testcaptcha m47xekl94ll1jlqjkv6seiq2zgrnypm 739872 739871 2026-04-30T11:23:17Z Test-mpa 73113 739872 wikitext text/x-wiki ����͓��e�̃e�X�g�ł��B�e�X�g�ԍ���2 this is an edit testcaptcha testcaptcha ib8p68e5lw6yjn4bsyh4iu8j9bpc8ba 739873 739872 2026-04-30T11:26:50Z Test-mpa 73113 739873 wikitext text/x-wiki ����͓��e�̃e�X�g�ł��B�e�X�g�ԍ���2 this is an edit testcaptcha testcaptcha testcaptcha 760yelsroyw02i24jw5zzqx2x300v1r Module:TNT 828 110019 739763 736463 2026-04-29T12:10:02Z UndueMarmot 73540 Copied from [[mw:Module:TNT]] (through [[mw:Special:Diff/8108055/cur]]) 739763 Scribunto text/plain -- -- INTRO: (!!! DO NOT RENAME THIS PAGE !!!) -- This module allows any template or module to be copy/pasted between -- wikis without any translation changes. All translation text is stored -- in the global Data:*.tab pages on Commons, and used everywhere. -- -- SEE: https://www.mediawiki.org/wiki/Multilingual_Templates_and_Modules -- -- ATTENTION: -- Please do NOT rename this module - it has to be identical on all wikis. -- This code is maintained at https://www.mediawiki.org/wiki/Module:TNT -- Please do not modify it anywhere else, as it may get copied and override your changes. -- Suggestions can be made at https://www.mediawiki.org/wiki/Module_talk:TNT -- -- DESCRIPTION: -- The "msg" function uses a Commons dataset to translate a message -- with a given key (e.g. source-table), plus optional arguments -- to the wiki markup in the current content language. -- Use lang=xx to set language. Example: -- -- {{#invoke:TNT | msg -- | I18n/Template:Graphs.tab <!-- https://commons.wikimedia.org/wiki/Data:I18n/Template:Graphs.tab --> -- | source-table <!-- uses a translation message with id = "source-table" --> -- | param1 }} <!-- optional parameter --> -- -- -- The "doc" function will generate the <templatedata> parameter documentation for templates. -- This way all template parameters can be stored and localized in a single Commons dataset. -- NOTE: "doc" assumes that all documentation is located in Data:Templatedata/* on Commons. -- -- {{#invoke:TNT | doc | Graph:Lines }} -- uses https://commons.wikimedia.org/wiki/Data:Templatedata/Graph:Lines.tab -- if the current page is Template:Graph:Lines/doc -- local config = (function() local ok, res = pcall(mw.loadData, "Module:TNT/config"); return ok and res or {}; end)(); local p = {} local i18nDataset = 'I18n/Module:TNT.tab' -- Forward declaration of the local functions local sanitizeDataset, loadData, link, formatMessage function p.msg(frame) local dataset, id local params = {} local lang = nil for k, v in pairs(frame.args) do if k == 1 then dataset = mw.text.trim(v) elseif k == 2 then id = mw.text.trim(v) elseif type(k) == 'number' then params[k - 2] = mw.text.trim(v) elseif k == 'lang' and v ~= '_' then lang = mw.text.trim(v) end end return formatMessage(dataset, id, params, lang) end -- Identical to p.msg() above, but used from other lua modules -- Parameters: name of dataset, message key, optional arguments -- Example with 2 params: format('I18n/Module:TNT', 'error_bad_msgkey', 'my-key', 'my-dataset') function p.format(dataset, key, ...) local checkType = require('libraryUtil').checkType checkType('format', 1, dataset, 'string') checkType('format', 2, key, 'string') return formatMessage(dataset, key, {...}) end -- Identical to p.msg() above, but used from other lua modules with the language param -- Parameters: language code, name of dataset, message key, optional arguments -- Example with 2 params: formatInLanguage('es', I18n/Module:TNT', 'error_bad_msgkey', 'my-key', 'my-dataset') function p.formatInLanguage(lang, dataset, key, ...) local checkType = require('libraryUtil').checkType checkType('formatInLanguage', 1, lang, 'string') checkType('formatInLanguage', 2, dataset, 'string') checkType('formatInLanguage', 3, key, 'string') return formatMessage(dataset, key, {...}, lang) end -- Obsolete function that adds a 'c:' prefix to the first param. -- "Sandbox/Sample.tab" -> 'c:Data:Sandbox/Sample.tab' function p.link(frame) return link(frame.args[1]) end local implGetTemplateData; function p.doc(frame) local dataset = sanitizeDataset(frame.args[1]) local json, dataPage, categories = implGetTemplateData(nil, dataset, frame.args) return frame:extensionTag('templatedata', json) .. formatMessage(i18nDataset, 'edit_doc', {link(dataPage)}) .. (categories or ""); end function p.getTemplateData(dataset) local data = implGetTemplateData(true, dataset); return data; end function p.getTemplateDataNew(...) return implGetTemplateData(nil, ...); end function implGetTemplateData(legacy, dataset, args) -- TODO: add '_' parameter once lua starts reindexing properly for "all" languages local data, dataPage, categories = loadData( dataset, nil, not legacy and 'TemplateData' or nil); local names = {} for _, field in ipairs(data.schema.fields) do table.insert(names, field.name) end local numOnly = true local params = {} local paramOrder = {} for _, row in ipairs(data.data) do local newVal = {} local name = nil for pos, columnName in ipairs(names) do if columnName == 'name' then name = row[pos] else newVal[columnName] = row[pos] end end if name then if ( (type(name) ~= "number") and ( (type(name) ~= "string") or not string.match(name, "^%d+$") ) ) then numOnly = false end params[name] = newVal table.insert(paramOrder, name) end end -- Work around json encoding treating {"1":{...}} as an [{...}] if numOnly then params['zzz123']='' end local json = mw.text.jsonEncode({ params=params, paramOrder=paramOrder, description=data.description, -- TODO: Store this in a dataset: format = (args and args.format or nil), }) if numOnly then json = string.gsub(json,'"zzz123":"",?', "") end return json, dataPage, categories; end -- Local functions sanitizeDataset = function(dataset) if not dataset then return nil end dataset = mw.text.trim(dataset) if dataset == '' then return nil elseif string.sub(dataset,-4) ~= '.tab' then return dataset .. '.tab' else return dataset end end loadData = function(dataset, lang, dataType) dataset = sanitizeDataset(dataset) if not dataset then error(formatMessage(i18nDataset, 'error_no_dataset', {})) end -- Give helpful error to thirdparties who try and copy this module. if not mw.ext or not mw.ext.data or not mw.ext.data.get then error(string.format([['''Missing JsonConfig extension, or not properly configured; Cannot load https://commons.wikimedia.org/wiki/Data:%s. See https://www.mediawiki.org/wiki/Extension:JsonConfig#Supporting_Wikimedia_templates''']], dataset)) end local dataPage = dataset; local data, categories; if dataType == 'TemplateData' then dataPage = 'TemplateData/' .. dataset; data = mw.ext.data.get(dataPage, lang); if data == false then data = mw.ext.data.get('Templatedata/' .. dataset, lang); if data ~= false then local legacyTemplateDataCategoryName = config.legacyTemplateDataCategoryName; if legacyTemplateDataCategoryName ~= false then categories = string.format( '[[Category:%s%s]]', legacyTemplateDataCategoryName or "Templates using legacy global TemplateData table name", config.translatableCategoryLink and mw.getCurrentFrame():callParserFunction("#translation:") or "" ); end dataPage = 'Templatedata/' .. dataset; end end else data = mw.ext.data.get(dataset, lang) end if data == false then if dataset == i18nDataset then -- Prevent cyclical calls error('Missing Commons dataset ' .. i18nDataset) else error(formatMessage(i18nDataset, 'error_bad_dataset', {link(dataPage)})) end end return data, dataPage, categories; end -- Given a dataset name, convert it to a title with the 'commons:data:' prefix link = function(dataset) return 'c:Data:' .. mw.text.trim(dataset or '') end formatMessage = function(dataset, key, params, lang) for _, row in pairs(loadData(dataset, lang).data) do local id, msg = unpack(row) if id == key then local result = mw.message.newRawMessage(msg, unpack(params or {})) return result:plain() end end if dataset == i18nDataset then -- Prevent cyclical calls error('Invalid message key "' .. key .. '"') else error(formatMessage(i18nDataset, 'error_bad_msgkey', {key, link(dataset)})) end end return p 2ii65lh3z6ssbx7s7r8h0vsbnbhbiyl User:Nardog/sandbox2.js 2 118608 739822 739170 2026-04-30T05:48:54Z Nardog 40946 739822 javascript text/javascript (async function listTools() { let pageAction = mw.config.get('wgAction'); let isView = pageAction === 'view'; let isEdit = ['edit', 'submit'].includes(pageAction); if (!isView && !isEdit) return; let pageType = mw.config.get('wgCanonicalSpecialPageName') || mw.config.get('wgNamespaceNumber'); if (isView && !pageType && !mw.config.exists('wgRedirectedFrom') && !mw.config.get('wgIsRedirect') && !mw.config.get('wgPageName').includes('/') ) { return; } await mw.loader.using([ 'mediawiki.util', 'mediawiki.Title', 'mediawiki.api', 'mediawiki.interface.helpers.styles' ]); mw.loader.addStyleTag(`.listtools:not(#mw-content-subtitle .listtools) { font-size: 85%; } .listtools, .listtools a { font-weight: normal !important; font-style: normal; } .mw-datatable .listtools { display: block; } .listtools + .mw-whatlinkshere-tools, #watchlist-edit-form .listtools ~ .mw-changeslist-links, .mw-special-DisambiguationPageLinks .listtools + a { display: none; }`); let messages = Object.assign({ watched: 'Added "$1" to your watchlist', watchFail: `Couldn't watch "$1"`, unwatchFail: `Couldn't unwatch "$1"` }, window.listtoolsMessages); let getMsg = (key, ...args) => ( Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key ); let notif; let watchHandler = async function (e) { e.preventDefault(); let $link = $(this); let $wrapper = $link.parent(); $link.detach(); let params = new URLSearchParams(this.search); let action = params.get('action'); $wrapper.text(getMsg(action + 'ing')); let pn = params.get('title').replaceAll('_', ' '); let promise = new mw.Api()[action](pn); if (notif) { notif.close(); notif = null; } try { let result = await promise; if (!result || !result[action + 'ed']) throw ''; let newAction = action === 'watch' ? 'unwatch' : 'watch'; params.set('action', newAction); $link.add(`.listtools-watch > a[href="${this.pathname + this.search}"]`) .attr('href', this.pathname + '?' + params) .text(getMsg(newAction)); if (action !== 'watch') return; let require = await mw.loader.using([ 'mediawiki.notification', 'mediawiki.watchstar.widgets' ]); notif = await mw.notify( new (require('mediawiki.watchstar.widgets'))('watch', pn, null, $.noop, { message: getMsg('watched', pn) }).$element, { tag: 'listtools' } ); } catch { notif = await mw.notify(getMsg(action + 'Fail', pn), { tag: 'listtools', type: 'error' }); } finally { $wrapper.html($link); } }; let extGetMain = function () { return this.title; }; let re = new RegExp(`(?:\\?title=|${ mw.util.escapeRegExp(mw.format(mw.config.get('wgArticlePath'), '')) })([^#&?]+)`); let processed = new WeakSet(); let processLinks = ($links, module, titles) => { let isBatch = !!titles; titles = titles || new Set(); $links.each(function (i) { if (processed.has(this)) return; let $link = $links.eq(i); let pn; if (module.useText) { pn = $link.text(); } else { let match = $link.attr('href')?.match(re); if (!match) return; pn = decodeURIComponent(match[1]); } let t = mw.Title.newFromText(pn); if (!t) return; if (module.titlesOnly) { let text = $link.text(); if (text !== pn.replaceAll('_', ' ') && (text !== t.getMainText() || t.namespace === 2) ) { return; } } if ($link.is('.external, .extiw')) { Object.assign(t, { getMain: extGetMain, host: this.host, namespace: 0, title: pn }); } else { if (t.namespace < 0) return; if ($link.hasClass('new')) { t.missing = true; } titles.add(t.getSubjectPage().toText()); } let $tools = $('<span>').addClass('listtools mw-changeslist-links') .data('listtools', t); tools.forEach(tool => { addTool($tools, tool); }); if ($link.is(':is(del, bdi) > :only-child')) { if (module.position === 'end') { $link.parent().parent().append(' ', $tools); } else { $link.parent().after(' ', $tools); } } else if (module.position === 'end') { $link.parent().append(' ', $tools); } else { $link.after(' ', $tools); } if (module.post) { module.post($tools); } processed.add(this); }); if (!isBatch) { getWatched(titles); } }; let tools = [ { name: 'edit', url: t => t.getUrl({ action: 'edit' }) }, { name: 'hist', url: t => !t.missing && t.getUrl({ action: 'history' }) }, { name: 'links', url: t => mw.util.getUrl('Special:WhatLinksHere/' + t) }, { name: 'watch', url: t => !t.host && t.getSubjectPage().getUrl({ action: 'watch' }), callback: watchHandler } ]; let addTool = ($tools, tool, escapedName) => { let t = $tools.data('listtools'); let $duplicate = escapedName && $tools.children('.listtools-' + escapedName); let url = tool.url; if (typeof url === 'function') { url = url(t); if (!url) { $duplicate?.remove(); return; } } let $link = $('<a>').attr('href', url).text(getMsg(tool.name)); if (t.host) { $link.prop('host', t.host); } if (tool.callback) { $link.on('click', tool.callback); } let $wrapper = $('<span>').addClass('listtools-' + tool.name) .append($link); let $next = tool.next && $tools.children('.listtools-' + tool.next); if ($next?.length) { $duplicate?.remove(); $next.before($wrapper); } else if ($duplicate?.length) { $duplicate.replaceWith($wrapper); } else { $tools.append($wrapper); } }; let extend = tool => { if (tool.label && !Object.hasOwn(messages, tool.label)) { messages[tool.name] = tool.label; } if (tool.next) { tool.next = $.escapeSelector(tool.next); } let existingTool = tools.find(t => t.name === tool.name); if (existingTool) { Object.assign(existingTool, tool); } else { tools.push(tool); } let escapedName = existingTool && $.escapeSelector(tool.name); let $allTools = $('.listtools'); $allTools.each(function (i) { addTool($allTools.eq(i), tool, escapedName); }); }; let getWatched = async titles => { if (!Array.isArray(titles)) { titles = [...titles].slice(0, 500); } if (!titles.length) return; (await new mw.Api().post({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages.forEach(page => { if (!page.watched) return; $(`.listtools-watch > a[href="${mw.util.getUrl(page.title, { action: 'watch' })}"]`) .attr('href', mw.util.getUrl(page.title, { action: 'unwatch' })) .text(getMsg('unwatch')); }); getWatched(titles.slice(50)); }; mw.hook('listtools.ready').fire(extend); let catTreeCallback = (records, observer) => { let $links = $(records[0].target).find('.CategoryTreeItem > bdi > a'); if ($links.length) { observer.takeRecords(); observer.disconnect(); processLinks($links, catTreeModule); } }; let catTreeModule = { selector: '.CategoryTreeItem > bdi > a', types: [14, 'CategoryTree'], position: 'end', post: $tools => { $tools.parent().next('.CategoryTreeChildren').each(function () { new MutationObserver(catTreeCallback) .observe(this, { childList: true }); }); } }; let modules = [ { selector: '#mw-pages li > a, #mw-pages li > span > a', types: [14] }, catTreeModule, { selector: '#mw-imagepage-section-linkstoimage a, #mw-imagepage-section-globalusage a', types: [6] }, { selector: '#mw-globalusage-result a', types: ['GlobalUsage'] }, { selector: '.mw-search-result-heading > a, .searchalttitle > a.mw-redirect, .iw-result__title > a, .mw-search-exists a', types: ['Search'] }, { selector: '.mw-search-createlink a', types: ['Search'], titlesOnly: true }, { selector: '#watchlist-edit-form .cdx-table td > label > a', types: ['EditWatchlist'] }, { selector: '.plainlinks > li > a', types: ['AbuseLog'], titlesOnly: true }, { selector: '#mw-allmessagestable td:first-child > a:first-child:not(.new)', types: ['Allmessages'], position: 'end' }, { selector: '.mw-spcontent li a', types: ['DisambiguationPageLinks', 'Listredirects'], titlesOnly: true }, { selector: 'li > a:first-child', types: ['FileDuplicateSearch'] }, { selector: '.TablePager_col_title > a:first-child, .TablePager_col_template > a', types: ['LintErrors'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: 'form > ul > li > a', types: ['Nuke'], position: 'end', titlesOnly: true }, { selector: '.page-assessments a', types: ['PageAssessments'], titlesOnly: true }, { selector: '.TablePager_col_pr_page > a', types: ['Protectedpages'], position: 'end' }, { selector: '#mw-content-text > ul a', types: ['Protectedtitles'], position: 'end' }, { selector: '.mw-fr-pending-changes-page-title', types: ['PendingChanges'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: '#mw-content-text > ul a:first-child', types: ['StablePages'], position: 'end' }, { selector: '.TablePager_col__page a', types: ['TopicSubscriptions'] }, { selector: '.undeleteResult > a', types: ['Undelete'], position: 'end', useText: true }, { selector: '.TablePager_col_img_name > a:first-child', // types: ['Listfiles'], position: 'end' }, { selector: '.mw-newpages-pagename', post: $tools => { let $nodes = $tools.parent().contents(); $nodes.slice( $nodes.index($tools) + 1, $nodes.index($nodes.filter('.mw-newpages-length')) ).replaceWith(' '); } }, { selector: '#mw-whatlinkshere-list li > bdi > a' }, { selector: '.mw-changeslist-log-entry > a:not(.mw-changeslist-log-gblblock a, .mw-changeslist-log-globalauth a)', titlesOnly: true }, { selector: '.mw-logevent-loglines > li:not(.mw-logline-gblblock, .mw-logline-globalauth) > a', types: ['Log'], titlesOnly: true }, { selector: '#mw-diff-otitle1 > strong > a, #mw-diff-ntitle1 > strong > a', types: ['ComparePages'], position: 'end' }, { selector: '#movepage-oldlink, #movepage-newlink', types: ['Movepage'] }, { selector: '.mw-undelete-revision a:not(.mw-userlink, .mw-usertoollinks > a)', types: ['Undelete'], useText: true }, { selector: '.galleryfilename, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a' }, { selector: '.mw-spcontent li a', position: 'end', titlesOnly: true } ]; if (isEdit) { let post = $tools => { if (!$tools[0].closest('.templatesUsed')) return; $tools.parent().contents().last().each(function () { this.textContent = this.textContent.slice(1); }).end().slice(-3, -1).remove(); }; let callback = mw.util.debounce(() => { processLinks( $('.mw-editfooter-list a, #wikiPreview > .previewnote a'), { titlesOnly: true, post } ); }, 500); mw.hook('wikipage.editform').add($form => { callback(); $form.find('.templatesUsed').each(function () { if (processed.has(this)) return; processed.add(this); new MutationObserver(callback) .observe(this, { childList: true, subtree: true }); }); }); } else if (typeof pageType === 'number') { $(() => { processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {}); }); } mw.hook('wikipage.content').add($content => { let titles = new Set(); let $links = $content.find('a'); modules.forEach(module => { if (module.types && !module.types.includes(pageType)) return; processLinks($links.filter(module.selector), module, titles); }); getWatched(titles); }); }()); mw.hook('listtools.ready').add(extend => { // extend({ // name: 'talk', // url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(), // next: 'hist' // }); extend({ name: 'subject', url: t => t.isTalkPage() && t.getSubjectPage().getUrl(), next: 'hist' }); extend({ name: 'last', url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }), next: 'links' }); // extend({ // name: 'purge', // url: t => t.getUrl({ action: 'purge' }), // next: 'watch', // callback: function (e) { // e.preventDefault(); // let $link = $(this); // let $wrapper = $link.parent(); // $link.detach(); // $wrapper.text('purging'); // let pn = $wrapper.closest('.listtools').data('listtools').toText(); // new mw.Api().post({ // action: 'purge', // forcelinkupdate: 1, // titles: pn, // formatversion: 2 // }).then(response => { // if (response.purge[0].purged) { // mw.notify(`Purged "${pn}"'`); // } // }).always(() => { // $wrapper.html($link); // }); // } // }); extend({ name: 'copy', url: '#', callback: function (e) { e.preventDefault(); let text = $(this).closest('.listtools').data('listtools').toText(); let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch (err) {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } } }); }); (mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') && mw.config.get('wgCanonicalSpecialPageName') !== 'GlobalContributions' && (function consecudiff() { mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}'); let isHist = mw.config.get('wgAction') === 'history'; class Consecudiff { constructor(lis, isContribs) { this.isContribs = isContribs; this.isEnhanced = !isHist && !isContribs && lis[0].classList.contains('mw-enhanced-rc'); this.threshold = isContribs ? window.consecudiffContribsThreshold || 120 : isHist ? window.consecudiffHistThreshold || 720 : window.consecudiffThreshold || 720; this.strictMode = !isContribs && !!window.consecudiffDetectInterruptions; this.diffSelector = isHist ? 'a.mw-history-histlinks-previous' : '.mw-changeslist-diff'; this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' || (isHist || isContribs) && 'a.mw-changeslist-date'; this.hybridSelector = this.diffSelector; if (this.permaSelector) { this.hybridSelector += ', ' + this.permaSelector; } this.topClass = isContribs ? 'mw-contributions-current' : 'mw-changeslist-last'; let dependencies = ['mediawiki.util']; if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') { dependencies.push('mediawiki.language.months'); } mw.loader.using(dependencies, () => { let chunks; if (isHist) { chunks = this.chunkByUser(lis); } else { chunks = []; this.groupByTitle(lis).forEach(group => { chunks.push(...this.chunkByUser(group)); }); } let subchunks = []; chunks.forEach(chunk => { subchunks.push(...this.divideByDate(chunk)); }); let linkPairs = []; subchunks.forEach(subchunk => { linkPairs.push(...this.makeLinks(subchunk)); }); linkPairs.forEach(([$span, parent]) => { $span.appendTo(parent); }); }); } groupByTitle(lis) { let selector = this.isContribs ? '.mw-contributions-title' : '.mw-changeslist-title'; let lisByTitle = {}; lis.forEach(li => { let link = (this.isEnhanced ? li.closest('table') : li) .querySelector(selector); if (!link) return; let title = link.textContent; if (!lisByTitle.hasOwnProperty(title)) { lisByTitle[title] = []; } lisByTitle[title].push(li); }); return Object.values(lisByTitle).filter(group => group.length > 1); } chunkByUser(lis) { if (this.isSingleContribs) { return [lis]; } let chunks = [], lastSplitAt = 0, prevUser; this.isSingleContribs = lis.some((li, i) => { let link = li.querySelector('.mw-userlink'); if (!link && this.isContribs) { return true; } let user = link && link.textContent; if (!link || i && user !== prevUser) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevUser = user; }); if (this.isSingleContribs) { return [lis]; } chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } divideByDate(lis) { let chunks = [], lastSplitAt = 0, prevDate; lis.forEach((li, i) => { let date; if (isHist || this.isContribs) { date = this.parseDate( li.querySelector('.mw-changeslist-date').textContent ); } else { date = Date.parse( li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z') ); } if (date) { date = date / 60000; } if (i && prevDate - date > this.threshold) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevDate = date; if (!this.strictMode || lastSplitAt === i) return; let prevDiff = lis[i - 1].querySelector(this.diffSelector); if (prevDiff) { let prevNext = mw.util.getParamValue('oldid', prevDiff.search); if (prevNext !== li.dataset.mwRevid) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } } }); chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } makeLinks(lis) { let count = lis.length; let firstPerma; let start = lis.findIndex(li => ( firstPerma = li.querySelector(this.hybridSelector) )); if (start === -1 || count - start < 2) return []; let end, lastDiff; for (let i = count - 1; i > start; i--) { if (!isHist && !this.isContribs) { lastDiff = lis[i].querySelector(this.diffSelector); if (lastDiff || lis[i].classList.contains('mw-changeslist-src-mw-new') ) { end = i + 1; break; } } if (this.permaSelector && lis[i].querySelector(this.permaSelector)) { end = i + 1; break; } } if (!end) return []; count = end - start; let params = { diff: lis[start].dataset.mwRevid }; if (lastDiff) { params.oldid = mw.util.getParamValue('oldid', lastDiff.search); } else { params.oldid = lis[end - 1].dataset.mwRevid; if (isHist && lis[end - 1].querySelector(this.diffSelector) || this.isContribs && !lis[end - 1].querySelector('.newpage') ) { params.direction = 'prev'; } } let title = !isHist && mw.util.getParamValue('title', firstPerma.search); let url = mw.util.getUrl(title, params); let classes = 'consecudiff'; if (!isHist && lis[start].classList.contains(this.topClass)) { classes += ' consecudiff-top'; } return lis.slice(start, end).map((li, i) => [ $('<span>').addClass(classes).append( $('<a>') .attr('href', url) .text(this.convertNumber(count - i + '/' + count)) ), this.isEnhanced ? li.tagName === 'TR' ? li.lastElementChild : li.querySelector('.mw-changeslist-line-inner') : li ]); } parseDate(s) { let date = Date.parse(s); if (date) { return date; } if (s.includes(',')) date = Date.parse(s.replace(',', '')); if (date) { return date; } if (mw.loader.getState('mediawiki.language.months') !== 'ready') return; s = s.replace(/\D/g, c => { let n = mw.language.convertNumber(c, true); return Number.isNaN(n) ? c : n; }); let h, m; s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => { h = $1; m = $2; return ' '; }); if (!h) return; let y, dateFirst; s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => { y = $2; dateFirst = /\d/.test($1); return $1 + ' '; }); if (!y) return; let mo, d; if (dateFirst) { [d, s] = this.getDate(s); if (!d) return; [mo, s] = this.getMonth(s); if (mo === -1) return; } else { [mo, s] = this.getMonth(s); if (mo === -1) return; [d, s] = this.getDate(s); if (!d) return; } return new Date(y, mo, d, h, m).getTime(); } getMonth(s) { if (!this.months) { this.months = mw.language.months.abbrev .concat(mw.language.months.names, mw.language.months.genitive) .reverse(); } let mo = this.months.findIndex(mn => { let temp = s.replace(mn, ' '); if (temp !== s) { s = temp; return true; } }); if (mo === -1) { let [numeric, temp] = this.getDate(s); numeric = parseInt(numeric); if (numeric > 0 && numeric < 13) { mo = numeric - 1; s = temp; } } else { mo = 11 - mo % 12; } return [mo, s]; } getDate(s) { let d; s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => { d = $2; return $1 + ' '; }); return [d, s]; } convertNumber(num) { try { return mw.language.convertNumber(num); } catch (e) { return num; } } } mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-body').each(function () { let lis = this.querySelectorAll('.mw-contributions-list > li'); if (lis.length > 1) { new Consecudiff([...lis], !isHist); } }); if (isHist) return; let $lists = $content.filter('.mw-changeslist'); if (!$lists.length) { $lists = $content.find('.mw-changeslist'); } $lists.each(function () { let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]'); if (lis.length > 1) { new Consecudiff([...lis]); } }); }); }()); if (mw.config.get('wgNamespaceNumber') === 14 && ( mw.config.get('wgAction') === 'view' || !mw.config.get('wgArticleId') )) { mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.UserInputWidget', 'mediawiki.widgets.datetime', 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement', 'mediawiki.interface.helpers.styles', 'user.options' ]); } $(function moveHistory() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Move history', 't-movehistory').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.moveHistoryDialog) { window.moveHistoryDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter', 'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions', 'mediawiki.interface.helpers.styles' ]); }); }); }); $(function sectionSearch() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Section search', 't-sectionsearch').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.sectionSearchDialog) { window.sectionSearchDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows', 'mediawiki.widgets', 'mediawiki.widgets.NamespacesMultiselectWidget' ]); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth' && mw.loader.using('jquery.tablesorter', function sortCentralAuthByEditCount() { mw.hook('wikipage.content').add($content => { let $table = $content.find('.mw-centralauth-wikislist').has('td'); if (!$table.length) return; $table.tablesorter().data('tablesorter').sort([{ 4: 'desc' }, { 1: 'asc' }]); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && [10, 828].includes(mw.config.get('wgNamespaceNumber')) && !mw.config.get('wgTitle').endsWith('/doc') && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoTestcases.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/TemplatePreviewGuard.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // $(function templatePreviewGuard() { // let button = document.querySelector('input[name="wpTemplateSandboxPreview"]'); // if (!button) return; // let proceed; // button.addEventListener('click', e => { // if (proceed) { // proceed = false; // return; // } // e.preventDefault(); // e.stopPropagation(); // let formData = new FormData(button.form); // let page = formData.get('wpTemplateSandboxPage'); // let temp = formData.get('wpTemplateSandboxTemplate'); // if (!page || !temp) return; // mw.loader.using('mediawiki.api').then(() => ( // new mw.Api().get({ // action: 'query', // titles: page, // prop: 'templates', // tltemplates: temp, // formatversion: 2 // }) // )).always(response => { // if (((((response || {}).query || {}).pages || [])[0] || {}).templates || // confirm(`"${page}" doesn't appear to transclude "${temp}". Continue?`) // ) { // proceed = true; // button.click(); // } // }); // }, true); // if (!mw.config.get('wgArticleId')) return; // let widgetEl = document.querySelector('#wpTemplateSandboxPage.oo-ui-widget'); // if (!widgetEl) return; // let pn = mw.config.get('wgPageName').replace(/_/g, ' '); // mw.loader.using(['mediawiki.api', 'oojs-ui-core']).then(() => ( // new mw.Api().get({ // action: 'query', // titles: pn, // prop: 'transcludedin', // tiprop: 'title', // tilimit: 'max', // formatversion: 2 // }) // )).then(response => { // if (!response.batchcomplete) return; // let pages = response.query.pages[0].transcludedin // .filter(o => o.title !== pn); // if (!pages.length) return; // let widget = OO.ui.infuse(widgetEl); // if (pages.length === 1) { // widget.setValue(pages[0].title); // return; // } // widget.$element.replaceWith( // new OO.ui.ComboBoxInputWidget({ // id: 'wpTemplateSandboxPage', // maxlength: widget.$input.prop('maxLength'), // name: widget.$input.prop('name'), // options: pages // .sort((a, b) => a.ns - b.ns || -(a.title < b.title)) // .map(o => ({ data: o.title })), // placeholder: widget.$input.prop('placeholder'), // tabIndex: widget.getTabIndex(), // value: widget.getValue() // }).on('enter', e => { // e.preventDefault(); // button.click(); // }).$element // ); // }); // }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(async () => { let form = document.getElementById('editform'); if (!form) return; let formData = new FormData(form); let section = formData.get('wpSection'); if (section === 'new') return; let widget = document.getElementById('wpSummaryWidget'); if (!widget) return; let isOld = formData.get('altBaseRevId') > 0 || (formData.get('baseRevId') || formData.get('parentRevId')) !== formData.get('editRevId'); await mw.loader.using([ 'jquery.textSelection', 'mediawiki.util', 'mediawiki.api', 'oojs-ui-core', 'oojs-ui.styles.icons-editing-core' ]); let $textarea = $('#wpTextbox1'); let input = OO.ui.infuse(widget); let button = new OO.ui.ButtonWidget({ framed: false, icon: 'undo', classes: ['autosectionlink-button'], invisibleLabel: true, label: 'Restore previous section link' }).toggle().on('click', () => { let cache = button.getData(); input.setValue(input.getValue().replace( /^(\/\*.*?\*\/)?\s*/, cache[0] ? '/* ' + cache[0] + ' */ ' : '' )); updatePreview(cache[0]); cache.reverse(); }).on('toggle', () => { input.$input.css('width', `calc(100% - ${button.$element.width()}px)`); }); input.$input.after(button.$element); let update = mw.util.debounce($diff => { let lines = $textarea.textSelection('getContents').trimEnd().split('\n'); let modLines = [...lines]; let firstLineNum; if (isOld) { let i, lastLineNum; $diff.find('td:last-child').each(function () { if (this.classList.contains('diff-lineno')) { i = this.textContent.replace(/\D+/g, '') - 1; } else if (this.classList.contains('diff-context')) { i++; } else if (this.classList.contains('diff-addedline')) { i++; if (!firstLineNum) { firstLineNum = i; } lastLineNum = i; } else if (this.classList.contains('diff-empty')) { if (!firstLineNum) { firstLineNum = i === 0 ? 1 : i; } lastLineNum = i; } }); modLines.length = lastLineNum || 0; } else { let origLines = $textarea.prop('defaultValue').trimEnd().split('\n'); firstLineNum = lines.findIndex((line, i) => line !== origLines[i]) + 1; if (!firstLineNum) { firstLineNum = lines.length < origLines.length ? lines.length : 1; } for (let i = 1, x = lines.length, y = origLines.length; (section ? i < x : i <= x) && lines[x - i] === origLines[y - i]; i++ ) { modLines.pop(); } } let re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/, lowest = 7; modLines.slice(firstLineNum).forEach(line => { let match = line.match(re); if (match?.[1].length < lowest) { lowest = match[1].length; } }); let head; modLines.slice(0, firstLineNum).reverse().some(line => { let match = line.match(re); if (match?.[1].length < lowest) { head = match[2]; return true; } }); if (head) { head = head .replace(/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>|<!--.*?-->|\[(?:https?:)?\/\/[^\s\[\]]+\s([^\]]+)\]/gi, '$1$2$3') .replace(/''(.+?)''/g, '$1') .trim(); } else if (modLines.length && section < 1 && lowest === 7 && lines.slice(modLines.length).some(line => re.test(line)) ) { head = ''; } let v = input.getValue(); let match = v.match(/^\/\*\s*(.+?)\s*\*\/\s*/); let prev = match?.[1]; if (prev === head) return; input.setValue(( head || head === '' ? '/* ' + head + ' */ ' : '' ) + (match ? v.slice(match[0].length) : v)); button.setData([prev, head]).toggle(true); updatePreview(head); }, 500); let updatePreview = head => { let $comment = $('.mw-summary-preview > .comment'); if (!$comment.length) return; let url; if (head) { url = mw.util.getUrl() + '#' + head.replace(/[\s_]+/g, '_'); } else if (head === '') { head = mw.messages.get('autocomment-top', '(top)'); url = mw.util.getUrl(); } let paren = [...mw.messages.get('parentheses', '($1)')][0]; let $nodes = $comment.contents(); let $ac = $nodes.eq(1); if ($nodes[0]?.textContent === paren && $ac.is('.autocomment:first-child')) { if (head) { $ac.children('a').attr('href', url).children('bdi').text(head); } else { if ($nodes[2]?.nodeType === 3) { $nodes[2].textContent = $nodes[2].textContent.trimStart(); } $ac.remove(); } } else if (head) { let rtl = document.body.classList.contains('sitedir-rtl'); $comment.prepend( paren, $('<span>').addClass('autocomment').append( $('<a>').attr({ href: url, title: mw.config.get('wgPageName').replaceAll('_', ' ') }).text(rtl ? '←' : '→').append( $('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(head) ), mw.messages.get('colon-separator', ': ') ) ); if ($nodes[0]?.nodeType === 3) { let text = $nodes[0].textContent; if (text.startsWith(paren)) { text = text.slice(paren.length); } $nodes[0].textContent = ' ' + text; } } }; if (isOld) { mw.hook('wikipage.diff').add(update); } else { $textarea.on('input', update); mw.hook('ext.CodeMirror.input').add(update); update(); } new mw.Api().loadMessagesIfMissing(['autocomment-top', 'colon-separator', 'parentheses']); mw.loader.addStyleTag('.autosectionlink-button{position:absolute;top:0;right:0;margin:0}'); }); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (function copyRevId() { let handler = function (e) { e.preventDefault(); let text = this.closest('.diff td')?.querySelector('[data-mw-revid]')?.dataset.mwRevid || this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!text) return; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); document.execCommand('copy'); $input.remove(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id') ) ); }); }()); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (() => { let handler = async function (e) { e.preventDefault(); let td = this.closest('.diff td'); let rev = td ? td.querySelector('[data-mw-revid]')?.dataset.mwRevid : this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!rev) { mw.notify(`Couldn't get the revision.`, { tag: 'markasunseen', type: 'error' }); return; } let pn = td ? new URLSearchParams([...td.querySelectorAll('a')].pop()?.search).get('title') : mw.config.get('wgPageName'); if (!pn) return; await mw.loader.using('mediawiki.api'); let result = (await new mw.Api().postWithEditToken({ action: 'setnotificationtimestamp', [td ? 'newerthanrevid' : 'torevid']: rev, titles: pn, formatversion: 2 })).setnotificationtimestamp?.[0]; if (Object.hasOwn(result, 'notificationtimestamp')) { mw.notify(`Marked revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'success' }); } else if (result?.notwatched) { mw.notify('This page is not on your watchlist.', { tag: 'markasunseen', type: 'warn' }); } else { mw.notify(`Couldn't mark revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions after this one as unseen' }).on('click', handler).text('unseen'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions since this one as unseen' }).on('click', handler).text('unseen') ) ); }); })(); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && ((mw.config.get('wgNamespaceNumber') % 2 || mw.config.get('wgNamespaceNumber') === 4) || (mw.config.get('wgWikiID') === 'metawiki' && mw.config.get('wgPageContentModel') === 'wikitext')) && mw.loader.using(['mediawiki.util', 'mediawiki.Title'], function copyUnsig() { let handler = function (e) { e.preventDefault(); let parent = this.closest('li, td'); let ts = parent.textContent.match(/\d\d:\d\d, \d\d? [A-Z][a-z]+ \d{4}/)?.[0]; if (!ts) return; let user = parent.querySelector('.mw-userlink').textContent; if (mw.util.isIPv6Address(user)) { user = user.toUpperCase(); } let temp = mw.util.isIPAddress(user) ? 'unsigned IP' : 'unsigned'; let text = `{{subst:${temp}|${user}|${ts}}}`; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').filter(function () { if (mw.config.get('wgWikiID') === 'metawiki') { return true; } let link = this.querySelector('strong > a') || this.parentElement.querySelector('#differences-prevlink, #differences-nextlink'); if (!link) return; let t = mw.Title.newFromText(mw.util.getParamValue('title', link.search)); return t.isTalkPage() || t.namespace === 4; }).append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig') ) ); }); }); // mw.config.get('wgAction') === 'history' && // mw.loader.using('mediawiki.util', function () { // mw.hook('wikipage.content').add($content => { // $content.find('a.mw-changeslist-date').after(function () { // return [ // ' (', // $('<a>').attr('href', mw.util.getUrl(null, { // action: 'edit', // oldid: this.closest('li').dataset.mwRevid // })).text('e'), // ')' // ]; // }); // }); // }); // ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && // mw.hook('wikipage.content').add($content => { // $content.find('.mw-changeslist-history').parent().after(function () { // return $('<span>').append( // $('<a>').attr( // 'href', // this.firstElementChild.getAttribute('href').slice(0, -7) + 'edit' // ).text('e') // ); // }); // }); if (screen.width < 500) { mw.loader.addStyleTag('@font-face{font-family:CharisW;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/017b2b2ad86e09d3c22b8cf0dfc78247/CharisSILRegular.ttf) format(truetype)} @font-face{font-family:CharisW;font-weight:700;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/6f5069ac6a300dad45383c952e92c573/CharisSILBold.ttf) format(truetype)} body .IPA{font-family:CharisW,sans-serif} .mw-highlight-lines > pre{width:120em}'); location.hash && $(() => { let target = document.querySelector(':target'); if (target?.getBoundingClientRect().top < 0) { target.scrollIntoView(); } }); } ['edit', 'submit'].includes(mw.config.get('wgAction')) && (mw.config.exists('wgCodeEditorCurrentLanguage') || mw.config.exists('cmMode') && mw.config.get('cmMode') !== 'mediawiki') && (function saveNEdit() { let notif; $(document.body).on('click', '#wpSave', async function (e) { if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey || e.originalEvent?.defaultPrevented ) { return; } e.preventDefault(); await mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'jquery.textSelection', 'oojs-ui-core' ]); let button = OO.ui.infuse(this.parentElement).setDisabled(true); let $textarea = $('#wpTextbox1'); let text = $textarea.textSelection('getContents'); let $summary = $('#wpSummary'); let formData = new FormData(this.form); let promise = new mw.Api().postWithEditToken({ action: 'edit', title: mw.config.get('wgPageName'), text: text, section: formData.get('wpSection') || undefined, summary: $summary.textSelection('getContents'), [$('#wpMinoredit').prop('checked') ? 'minor' : 'notminor']: 1, baserevid: formData.get('editRevId'), basetimestamp: formData.get('wpEdittime'), starttimestamp: formData.get('wpStarttime'), watchlist: $('#wpWatchthis').prop('checked') ? 'watch' : 'unwatch', watchlistexpiry: formData.get('wpWatchlistExpiry') || undefined, undo: formData.get('wpUndidRevision') || undefined, undoafter: formData.get('wpUndoAfter') || undefined, contentformat: formData.get('format'), contentmodel: formData.get('model'), assertuser: mw.config.get('wgUserName'), formatversion: 2 }); notif?.close(); notif = null; try { let response = await promise; if (response?.edit?.result !== 'Success') throw ''; $('#editform > input[name="wpUndidRevision"], #editform > input[name="wpUndoAfter"]').remove(); $textarea.data('origtext', text).prop('defaultValue', text); $summary.val($summary.prop('defaultValue')); if (mw.loader.getState('mediawiki.editRecovery.edit') === 'ready') { let storage = mw.loader.moduleRegistry['mediawiki.editRecovery.edit'].packageExports['storage.js']; storage.deleteData(mw.config.get('wgPageName')); storage.closeDatabase(); } notif = await mw.notify(response.edit.nochange ? 'No change' : [ document.createTextNode('Saved'), $('<p>').append( new OO.ui.ButtonWidget({ href: mw.util.getUrl(), target: '_blank', label: 'View' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { diff: response.edit.newrevid || 'cur', diffonly: 1 }), target: '_blank', label: 'Diff' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { action: 'history' }), target: '_blank', label: 'History' }).$element )[0] ], { tag: 'savenedit' }); } catch (error) { notif = await mw.notify(error?.error?.info || error || 'Save failed', { autoHideSeconds: 'long', tag: 'savenedit', type: 'error' }); } finally { button.setDisabled(); } }); }()); mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgAction') === 'view' && mw.config.get('wgCategories')?.some(c => c.endsWith(' actors') || c.endsWith(' actresses')) && $(() => { let n = $.escapeSelector(mw.config.get('wgTitle').replace(/ \(.+\)$/, '')); let $links = $(`.hatnote a[title$="${n} filmography"], .hatnote a[title*="${n} on "], .hatnote a[title*="${n} performances"]`); if (!$links.length) return; let titles = {}; $links = $links.filter(function () { let text = this.textContent; return !(titles[text] = Object.hasOwn(titles, text)); }); mw.notify( $links.length === 1 ? $links.clone() : $('<ul>').append($links.clone().wrap('<li>').parent()), { autoHideSeconds: 'long' } ); }); ['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(mw.config.get('wgCanonicalSpecialPageName')) && $.when($.ready, mw.loader.using([ 'user.options', 'mediawiki.util', 'mediawiki.api' ])).then(function rcMuter() { let os = mw.user.options.get('userjs-rcmuter'); let set = new Set(os && os.split('|')); let save = () => { let ns = [...set].join('|'); if (ns === mw.user.options.get('userjs-rcmuter')) return; new mw.Api().saveOption('userjs-rcmuter', ns); mw.user.options.set('userjs-rcmuter', ns); $edit.attr('data-rcmuter', set.size); }; mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}'); let $edit = $('<a>').attr({ class: 'rcmuter-edit', href: '#', 'data-rcmuter': set.size }).text('Edit muted').on('click', e => { e.preventDefault(); mw.loader.using([ 'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget' ]).then(() => OO.ui.getWindowManager().getWindow('message')).then(dialog => { let multiselect = new mw.widgets.UsersMultiselectWidget({ $overlay: dialog.$overlay, ipAllowed: true, selected: [...set] }).connect(dialog, { change: 'updateSize', reorder: 'updateSize' }); let instance = dialog.open({ message: $([ document.createTextNode('Muted users:'), multiselect.$element[0] ]), size: 'medium' }); instance.opened.then(() => { setTimeout(() => { multiselect.focus().menu.toggle(false); }); }); instance.closed.then(result => { if (!result || result.action !== 'accept') return; set = new Set(multiselect.getSelectedUsernames()); save(); mw.notify('Changes will take effect in next load.', { tag: 'rcmuter' }); }); }); }); let buttonsShown; let $toggleButtons = $('<a>').attr('href', '#').text('Show toggle buttons').on('click', function (e) { e.preventDefault(); if (buttonsShown) { mw.hook('wikipage.content').remove(addButtons); $('.rcmuter-toggle').remove(); this.textContent = 'Show toggle buttons'; } else { mw.hook('wikipage.content').add(addButtons); this.textContent = 'Hide toggle buttons'; } buttonsShown = !buttonsShown; }); let $toggle = $('<a>').attr({ class: 'rcmuter-togglemuted', href: '#' }).text('Show muted').on('click', function (e) { e.preventDefault(); this.textContent = document.body.classList.toggle('rcmuter-disabled') ? 'Hide muted' : 'Show muted'; }); let $toggleSpan = $('<span>').hide().append($toggle); mw.util.addSubtitle( $('<span>').addClass('mw-changeslist-links').append( $('<span>').append($edit), $('<span>').append($toggleButtons), $toggleSpan )[0] ); let toggle = function (e) { e.preventDefault(); let user = $(this) .closest('.mw-userlink ~ .mw-usertoollinks, .mw-changeslist-line-inner-userLink ~ .mw-changeslist-line-inner-userTalkLink') .prevAll('.mw-userlink, .mw-changeslist-line-inner-userLink') .last().text().trim(); if (!user) { mw.notify(`Can't retrieve the username.`, { tag: 'rcmuter', type: 'error' }); return; } let muting = this.parentElement.classList.toggle('rcmuter-unmute'); set[muting ? 'add' : 'delete'](user); save(); this.textContent = muting ? 'unmute' : 'mute'; mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, { tag: 'rcmuter' }); }; let addButtons = $content => { if (!$content.is('#mw-content-text, .mw-changeslist')) { $content = $('#mw-content-text'); if ($content.has('.rcmuter-toggle').length) return; } let $tools = $content.find('.mw-usertoollinks.mw-changeslist-links'); let $muted = $tools.filter('.rcmuter-muted *'); $tools.not($muted).append( $('<span>').addClass('rcmuter-toggle').append( $('<a>').attr('href', '#').text('mute').on('click', toggle) ) ); if (!$muted.length) return; $muted.append( $('<span>').addClass('rcmuter-toggle rcmuter-unmute').append( $('<a>').attr('href', '#').text('unmute').on('click', toggle) ) ); }; let mutedCount; let filter = function () { let muted = set.has(this.textContent); if (muted) mutedCount++; return muted; }; mw.hook('wikipage.content').add($content => { if (!$content.is('#mw-content-text, .mw-changeslist')) return; if (!set.size) { $toggleSpan.hide(); return; } mutedCount = 0; $content.find('.changedby > .mw-userlink:only-child') .filter(filter).closest('table').addClass('rcmuter-muted'); $content.find('.mw-userlink:not(.changedby > *, .comment *, .rcmuter-muted *)') .filter(filter).closest('.mw-changeslist-line, table').addClass('rcmuter-muted') .closest('table.mw-enhanced-rc').find('.changedby > .mw-userlink').filter(filter).addClass('rcmuter-muted'); $toggleSpan.toggle(!!mutedCount); $toggle.attr('data-rcmuter', mutedCount); }); }); location.hostname.endsWith('.wikipedia.org') && mw.config.get('wgNamespaceNumber') % 2 === 0 && // mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.util')).then(function refRenamer() { if (!document.getElementById('p-tb')) return; let messages = Object.assign({ portlet: 'RefRenamer', loading: 'Loading RefRenamer...' }, window.refrenamerMessages); let clicked; mw.util.addPortletLink('p-tb', '#', messages.portlet, 't-refrenamer').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.refRenamer) { window.refRenamer(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript'); mw.notify(messages.loading, { autoHideSeconds: 'long', tag: 'refrenamer' }); }); }); if (['edit', 'submit'].includes(mw.config.get('wgAction'))) { mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/ExpandContractions.js&action=raw&ctype=text/javascript', 's'); mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/Unpipe.js&action=raw&ctype=text/javascript', 's'); } mw.config.get('wgAction') !== 'history' && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopyCodeBlock.js&action=raw&ctype=text/javascript', 's'); mw.config.exists('wgDiffNewId') && mw.config.get('wgDiscussionToolsFeaturesEnabled') && (function () { let data = {}, clickHandler, autoClear, run; window.dtc = data; let highlight = revId => { let ids = data[revId]; if (!ids || !ids.length) return; mw.loader.moduleRegistry['ext.discussionTools.init'].packageExports['highlighter.js'] .highlightNewComments(mw.dt.pageThreads, true, ids); if (clickHandler) { $(document.body).off('click', clickHandler); return; } $._data(document.body, 'events').click.some(o => { if (String(o.handler).includes('highlighter.clearHighlightTargetComment(')) { $(document.body).off('click', o.handler); clickHandler = o.handler; return true; } }); $._data(window, 'events').popstate.some(o => { if (String(o.handler).includes('highlighter.highlightTargetComment(')) { $(window).off('popstate', o.handler); return true; } }); }; let scroll = revId => { let ids = data[revId]; if (!ids || !ids.length) return; let yToSpan = Object.fromEntries( ids.map(id => document.getElementById(id)).filter(Boolean) .map(span => [span.getBoundingClientRect().y, span]) ); let ys = Object.keys(yToSpan); if (!ys.length) return; let lower = ys.filter(y => y > 10); if (!lower.length || Math.max(...lower) < document.documentElement.clientHeight ) { yToSpan[Math.min(...ys)].scrollIntoView(); } else { yToSpan[Math.min(...lower)].scrollIntoView(); } }; let scrollToNext = function (e) { e.preventDefault(); let revId = mw.config.get('wgDiffOldId'); if (!revId || !data[revId]) return; let i = data[revId].indexOf( this.closest('[data-mw-thread-id]').dataset.mwThreadId ); if (i === -1) return; let next = data[revId][i + 1] || data[revId][0]; document.getElementById(next).scrollIntoView(); }; mw.hook('wikipage.content').add(async $content => { let revId = mw.config.get('wgDiffOldId'); if (!revId) return; let param = new URLSearchParams(location.search).get('diffonly'); if (param && param !== '0') return; if (data[revId]) { highlight(revId); return; } await mw.loader.using(['ext.discussionTools.init', 'mediawiki.util']); let begin = Date.parse($('#mw-diff-otitle1 .mw-diff-timestamp').data('timestamp')); data[revId] = mw.dt.pageThreads.getCommentItems() .filter(c => c.timestamp > begin).map(c => c.id); if (!data[revId].length) return; await new Promise(setTimeout); highlight(revId); $content.find('.ext-discussiontools-init-replylink-buttons').filter(function () { return data[revId].includes(this.dataset.mwThreadId); }).children('span:last-of-type').before( ' | ', $('<a>').attr({ href: '#', role: 'button' }).text('next').on('click', scrollToNext) ); if (run || !document.getElementById('p-tb')) return; run = true; let portlet = mw.util.addPortletLink('p-tb', '#', 'Scroll to next', 't-scrolltonext'); portlet.firstElementChild.addEventListener('click', e => { e.preventDefault(); scroll(mw.config.get('wgDiffOldId')); }); mw.util.addPortletLink('p-tb', '#', 'Toggle highlight', 't-togglehighlight').firstElementChild.addEventListener('click', e => { e.preventDefault(); autoClear = !autoClear; if (autoClear) { $(document.body).on('click', clickHandler)[0].click(); } else { highlight(mw.config.get('wgDiffOldId')); } }); mw.loader.addStyleTag(`#t-scrolltonext{position:fixed;bottom:${portlet.clientHeight}px} #t-togglehighlight{position:fixed;bottom:0}`); }); }()); mw.config.get('wgNamespaceNumber') === 6 && mw.config.get('wgAction') === 'view' && mw.hook('wikipage.content').add($content => { $content.find('.filehistory .mw-usertoollinks-contribs').after(function () { return [ ' | ', $('<a>').attr('href', `${ mw.config.get('wgScript') }?title=Special:ListFiles/${ this.pathname.replace(/^.+\//, '') }&ilshowall=1`).text('uploads') ]; }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(function () { if (!$('input[name="wpSection"]').val()) return; mw.hook('wikipage.content').add(async $content => { let $refs = $content.find('.mw-ext-cite-warning-sectionpreview_no_text'); if (!$refs.length) return; let ids = {}; $refs.each(function () { ids[this.closest('[id]').id.replace(/-\d+$/, '')] = this; }); let response = await $.get(`/api/rest_v1/page/html/${encodeURIComponent(mw.config.get('wgPageName'))}`); $($.parseHTML(response)).find('.mw-reference-text').each(function () { ids[this.id.replace(/^mw-reference-text-|-\d+$/g, '')]?.replaceWith(this); }); }); }); mw.hook('moremenu.ready').add(config => { $('#mm-page-purge-cache > a').on('click', e => { e.preventDefault(); new mw.Api().post({ action: 'purge', forcelinkupdate: 1, titles: config.page.name, formatversion: 2 }).then(() => { location.href = mw.util.getUrl(); }); }); $('#mm-page-search-search-history-wikiblame > a').on('click', function (e) { e.preventDefault(); let q = prompt(); if (q === null) return; let href = this.href; if (q) { let removal = q[0] === '!'; if (removal) { q = q.slice(1); } href += '&needle=' + encodeURIComponent(q); if (removal) { href += '&binary_search_inverse=on'; } href += '&force_wikitags=on'; } open(href, '_blank'); }); $('#mm-page-expand-templates > a').on('click auxclick', function (e) { if (e.which > 2) return; e.preventDefault(); let revId = mw.config.get('wgRevisionId') || Number($('input[name=oldid]').val()); let url = revId ? '/w/rest.php/v1/revision/' + revId : '/w/rest.php/v1/page/' + config.page.encodedName; $.get(url).then(response => { $('<form>').attr({ method: 'post', action: this.href, target: '_blank' }).append( [ ['wpInput', response.source], ['wpContextTitle', config.page.name], ['wpRemoveComments', 1] ].map(([n, v]) => $('<input>').attr({ name: n, type: 'hidden' }).val(v)) ).appendTo(document.body).trigger('submit').remove(); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'ApiSandbox' && mw.hook('apisandbox.formatRequest').add((...args) => { args[4].complete = function () { setTimeout(() => { mw.hook('wikipage.content').fire($('.oo-ui-pageLayout-active')); }, 100); }; }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.storage')).then(async () => { let infuseAndCall = (query, method, ...args) => { let $widget = $(query); if ($widget.length) { return OO.ui.infuse($widget)[method](...args); } }; let section = $('input[name="wpSection"]').val(); if (section) { let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let save = () => { let newSource = $textarea.textSelection('getContents'); if (newSource === source) { mw.storage.session.remove('editfullpage'); } else { mw.storage.session.setObject('editfullpage', [ mw.config.get('wgPageName'), section, newSource.trimEnd(), infuseAndCall('#wpSummaryWidget', 'getValue') || '', Number(infuseAndCall('#wpMinoreditWidget', 'isSelected')) || 0, Number(infuseAndCall('#wpWatchthisWidget', 'isSelected')) || 0, infuseAndCall('#wpWatchlistExpiryWidget', 'getValue') || 'infinite' ]); } }; await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); setInterval(() => { mw.requestIdleCallback(save); }, 3000); window.addEventListener('beforeunload', save); return; } let data = mw.storage.session.getObject('editfullpage'); mw.storage.session.remove('editfullpage'); console.log(data); if (!data || data[0] !== mw.config.get('wgPageName')) return; let isNew = data[1] === 'new'; let isLead = data[1] === '0'; let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let newSource, start, msg, notifOpts = { autoHideSeconds: 'long' }; let orig = []; if (isNew) { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); newSource = source + (data[3] ? '\n== ' + data[3] + ' ==\n\n' : '\n') + data[2] + '\n'; start = source.length; } else { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core', 'mediawiki.api']); let { parse } = await new mw.Api().get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'sections', wrapoutputclass: '', disablelimitreport: 1, disableeditsection: 1, disabletoc: 1, formatversion: 2 }); let target = !isLead && parse.sections.find(s => s.index === data[1]); if (isLead || target) { let next = parse.sections.find(s => s.index - 1 === Number(data[1])); newSource = (isLead ? '' : [...source].slice(0, target.byteoffset)).join('') + data[2] + (next ? '\n\n' + [...source].slice(next.byteoffset).join('') : '\n'); start = isLead ? 0 : target.byteoffset; } else { newSource = source + '\n\n' + data[2] + '\n'; start = source.length; msg = `Section restored. Couldn't find the section. The source is appended at bottom.`; notifOpts.type = 'warn'; } orig[0] = infuseAndCall('#wpSummaryWidget', 'getValue'); infuseAndCall('#wpSummaryWidget', 'setValue', data[3]); } $textarea.textSelection('setContents', newSource); orig[1] = infuseAndCall('#wpMinoreditWidget', 'getSelected'); infuseAndCall('#wpMinoreditWidget', 'setSelected', data[4]); orig[2] = infuseAndCall('#wpWatchthisWidget', 'getSelected'); infuseAndCall('#wpWatchthisWidget', 'setSelected', data[5]); orig[3] = infuseAndCall('#wpWatchlistExpiryWidget', 'getValue'); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', data[6]); setTimeout(() => { $textarea.textSelection('setSelection', { start }); }); let notif = await mw.notify($([ document.createTextNode(msg || 'Section restored.'), $('<p>').append( new OO.ui.ButtonWidget({ flags: 'destructive', label: 'Discard' }).on('click', () => { $textarea.textSelection('setContents', source); if (orig[0]) { infuseAndCall('#wpSummaryWidget', 'setValue', orig[0]); } infuseAndCall('#wpMinoreditWidget', 'setSelected', orig[1]); infuseAndCall('#wpWatchthisWidget', 'setSelected', orig[2]); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', orig[3]); notif.close(); }).$element )[0] ]), notifOpts); }); mw.config.exists('wgPostEdit') && mw.loader.using('mediawiki.storage', () => { mw.storage.session.remove('editfullpage'); }); mw.config.get('wgAction') === 'history' && mw.hook('wikipage.content').add(async $content => { if (!$content.has('.mw-history-line-updated').length) return; let href = $content.find('a.mw-history-histlinks-current:not(.mw-history-line-updated a)').attr('href'); if (!href) { await mw.loader.using(['mediawiki.api', 'mediawiki.util']); let page = (await new mw.Api().get({ action: 'query', titles: mw.config.get('wgPageName'), prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev >= page.lastrevid) return; href = mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); } $content.find('.mw-history-compareselectedversions-button').first().after( ' ', $('<a>').attr({ class: 'unseendiff', href: href }).text('unseen') ); }); (async () => { let cspn = mw.config.get('wgCanonicalSpecialPageName'); let isBp = cspn === 'Blankpage'; if (!isBp && cspn !== 'Watchlist') return; await mw.loader.using('mediawiki.util'); let notify = async (text, options, pn) => { let msg = [document.createTextNode(text)]; if (pn) { msg.push( $('<p>').append( $('<a>').attr('href', mw.util.getUrl(pn)).text(pn), ' ', $('<span>').addClass('mw-changeslist-links').append( $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'edit' })) .text('edit') ), $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'history' })) .text('history') ) ) )[0] ); } if (isBp) { await $.ready; $('#mw-content-text').html(msg); } else { return mw.notify(msg, Object.assign(options || {}, { tag: 'unseendiff' })); } }; let getUrl = async pn => { await mw.loader.using('mediawiki.api'); let page = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; if (!page.notificationtimestamp) { notify(`Couldn't get the last seen time.`, { type: 'warn' }, pn); return; } let rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (rev === page.lastrevid) { notify('Already seen.', { type: 'warn' }, pn); return; } if (!rev) { rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, rvdir: 'newer', formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev) { notify(`Couldn't get the last seen revision.`, { type: 'warn' }, pn); return; } } if (rev > page.lastrevid) { notify(`Invalid rev for "${pn}" (rev: ${rev}, lastrevid: ${page.lastrevid})`, { autoHideSeconds: 'long', type: 'warn' }, pn); return; } return mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); }; if (isBp) { let pn = mw.config.get('wgTitle').match(/^[^/]+\/unseendiff\/(.+)$/)?.[1]; if (!pn) return; notify('Loading...', null, pn); let href = await getUrl(pn); if (!href) return; notify('Redirecting...', null, pn); location.href = href; return; } let handler = async function (e) { if (e.which > 2) return; e.preventDefault(); let pn = this.dataset.pn; if (!pn) { notify(`Couldn't get the page name.`, { type: 'error' }); return; } let notifPromise = notify('Loading...', { autoHideSeconds: 'long' }); let href = await getUrl(pn); if (!href) return; $(`.unseendiff-loader[data-pn="${$.escapeSelector(pn)}"]`).attr({ class: 'unseendiff', href: href, target: '_blank' }).off('click auxclick', handler); if (e.type === 'auxclick' || e.ctrlKey || e.metaKey || e.shiftKey) { open(href); } else { this.click(); } (await notifPromise).close(); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-changeslist-src-mw-edit.mw-changeslist-watchedunseen:not(.mw-changeslist-watchedseen) .mw-changeslist-line-inner' ).each(function () { let pn = this.dataset.targetPage || this.closest('[data-target-page]')?.dataset.targetPage || this.closest('table.mw-enhanced-rc')?.querySelector('[data-target-page]')?.dataset.targetPage; if (!pn) return; $('<span>').append( $('<a>').attr({ class: 'unseendiff-loader', href: mw.util.getUrl(`Special:BlankPage/unseendiff/${pn}`), 'data-pn': pn }).on('click auxclick', handler).text('unseen') ).appendTo( [...this.querySelectorAll('.mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); }); })(); ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.using('mediawiki.util', () => { let watched = new Set(); let query = async lis => { let titles = Object.keys(lis).slice(0, 50); if (!titles.length) return; await mw.loader.using('mediawiki.api'); let pages = (await new mw.Api().post({ action: 'query', titles: titles, prop: 'info', inprop: 'notificationtimestamp|watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages; for (let page of pages) { if (!Object.hasOwn(lis, page.title)) continue; if (page.watched) { watched.add(page); $(lis[page.title]).addClass('watched'); } if (!page.notificationtimestamp) continue; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev === page.lastrevid) continue; if (rev > page.lastrevid) { mw.notify($([ document.createTextNode('Invalid rev for "'), $('<a>').attr({ href: mw.util.getUrl(page.title, { action: 'history' }), target: '_blank' }).text(page.title)[0], document.createTextNode(`" (rev: ${rev}, lastrevid: ${page.lastrevid})`), ]), { autoHideSeconds: 'long', type: 'warn' }); continue; } $('<span>').append( $('<a>').attr({ class: 'unseendiff', href: mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }) }).text('unseen') ).appendTo( lis[page.title].map(li => ( [...li.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(li).before(' ') )) ); } titles.forEach(title => { delete lis[title]; }); query(lis); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-contributions-list > li:not(.mw-contributions-current)[data-mw-revid]' ).each(function () { let link = this.querySelector('a.mw-changeslist-date, a.mw-changeslist-history'); let pn = link ? new URLSearchParams(link.search).get('title') : ''; $('<span>').append( $('<a>').attr({ class: 'mw-changeslist-diff', href: mw.util.getUrl(pn, { diff: 'cur', oldid: this.dataset.mwRevid }) }).text('cur') ).appendTo( [...this.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); if (mw.config.get('wgWikiID') === 'wikidatawiki') return; let lis = {}; $content.find('.mw-contributions-title').each(function () { let title = this.textContent; if (!Object.hasOwn(lis, title)) { lis[title] = []; } lis[title].push(this.closest('li')); }); Object.keys(lis).forEach(title => { if (watched.has(title)) { $(lis[title]).addClass('watched'); delete lis[title]; } }); query(lis); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox9.js&action=raw&ctype=text/javascript'); mw.config.get('wgWikiID') === 'metawiki' && (async () => { let css = mw.loader.addStyleTag(`.wishtitle { font-size: 90%; font-style: italic; word-break: break-word; } .wishtitle > a { color: var(--color-warning, #886425); } .wishtitle > a:visited { color: var(--border-color-warning--hover, #735421); } .wishtitle-declined > a { text-decoration: line-through; } .wishtitle-declined > a:hover, .wishtitle-declined > a:focus, .mw-underline-always .wishtitle-declined > a { text-decoration: line-through underline; } #watchlist-edit-form .wishtitle { display: inline-block; } .mw-search-result-heading > .wishtitle, .catchangesviewer-table .wishtitle { display: block; } .catchangesviewer-table:has(.wishtitle) { white-space: wrap; }`); let lang = mw.config.get('wgUserLanguage'); let titles; let loadTitles = async () => { await mw.loader.using('mediawiki.storage'); titles = titles || mw.storage.getObject('wishtitles'); if (titles?.lang !== lang) { titles = { lang, w: [], fa: [] }; } }; let updateTitles = async (crwstatuses, crwcontinue) => { await mw.loader.using('mediawiki.api'); let params = { action: 'query', list: 'communityrequests-wishes', crwlang: lang, crwstatuses: crwstatuses, crwprop: 'title|updated', crwsort: 'updated', crwdir: 'ascending', crwlimit: 'max', crwcontinue: crwcontinue, formatversion: 2 }; if (!crwcontinue && !crwstatuses && titles._) { params.crwcontinue = `|${titles._}|0`; } let response = await new mw.Api().get(params); let wishes = response?.query?.['communityrequests-wishes']; if (wishes?.length) { let $span = $('<span>'); wishes.forEach(w => { let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1]; if (!id) return; titles.w[id - 1] = $span.html(w.title).text(); if (crwstatuses === 'declined') { (titles.wd = titles.wd || []).push(id - 1); } let faId = w.crfatitle?.match(/^Community Wishlist\/FA(\d+)/)?.[1]; if (!faId) return; titles.fa[faId - 1] = w.focusareatitle; }); if (!crwstatuses) { titles._ = wishes.at(-1).updated.replace(/\D/g, ''); } } let expiry = 86400; if (crwstatuses || crwcontinue) { let prev = mw.storage.getObject('_EXPIRY_wishtitles'); if (prev) { expiry = Math.round(Date.now() / 1000) + 86400 - prev; } } mw.storage.setObject('wishtitles', titles, expiry); crwcontinue = response?.continue?.crwcontinue; if (crwcontinue) { await updateTitles(crwstatuses, crwcontinue); } }; let getTitle = id => ( id[0] === 'W' ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1] ); let renderTitle = (title, id, tag = 'span') => { let classes = 'wishtitle'; if (id[0] === 'W' && titles.wd?.includes(id.slice(1) - 1)) { classes += ' wishtitle-declined'; } return $(`<${tag}>`).addClass(classes).append( $('<a>').attr({ href: `/wiki/Community_Wishlist/${id}`, title: `Community Wishlist/${id}` }).text(title) ); }; let callback = ([id, links]) => { let title = getTitle(id); if (!title) { return true; } $(links).after(' ', renderTitle(title, id)); }; let selector = '.mw-changeslist-title, ' + '.mw-changeslist-log-entry > a:not(.mw-userlink), ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' + '#watchlist-edit-form .cdx-table td > label > a, ' + '.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' + '.mw-contributions-title, ' + '#mw-whatlinkshere-list li > bdi > a, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-logevent-loglines > li > a, ' + '#mw-pages li > a, ' + '.catchangesviewer-table td:nth-child(3) > a'; mw.hook('wikipage.content').add(async $content => { let links = {}; $content.find('a').each(function () { if (!this.matches(selector)) return; let id = this.textContent.match( /^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/ )?.[1]; if (!id) return; (links[id] = links[id] || []).push(this); }); links = Object.entries(links); if (!links.length) return; await loadTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles('declined'); links.forEach(callback); }); let pn = mw.config.get('wgRelevantPageName'); let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1]; if (!id) return; await $.ready; let extTitle = document.querySelector('.ext-communityrequests-wish--title'); if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return; await loadTitles(); let title = getTitle(id); if (!title) { await updateTitles(); title = getTitle(id); if (!title) { await updateTitles('declined'); title = getTitle(id); if (!title) return; } } let $title = renderTitle(title, id, 'div'); if (mw.config.get('skin') === 'vector-2022') { $title.prependTo('.vector-page-toolbar'); } else { $title.insertAfter('#firstHeading'); } css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}'; if (extTitle) return; document.title = document.title.replace( pn.replaceAll('_', ' '), `${pn.replace(`Community_Wishlist/${id}`, title)} ($&)` ); })(); mw.config.get('wgWikiID') === 'metawiki' && mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => { if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return; let title = mw.config.get('wgTitle'); if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return; if (isWatched) { await new mw.Api().watch(title + '/Votes', expiry); mw.notify('Watching /Votes too.'); } else { await new mw.Api().unwatch(title + '/Votes'); mw.notify('Unwatched /Votes too.'); } }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && $(async () => { let $input = $('#wpTemplateSandboxTemplate'); if (!$input.length) return; mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}'); let makeTemplateField = () => new OO.ui.FieldLayout( new mw.widgets.TitleInputWidget({ inputId: 'wpTemplateSandboxTemplate', name: 'wpTemplateSandboxTemplate', showMissing: false, value: $input.val() }), { label: 'Template name:' } ); if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') { await mw.loader.using('mediawiki.widgets'); $input.parent().replaceWith(makeTemplateField().$element); return; } let require = await mw.loader.using([ 'ext.TemplateSandbox.TemplateSandboxTitleWidget', 'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options' ]); let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({ $overlay: true, id: 'wpTemplateSandboxPage', maxLength: 255, name: 'wpTemplateSandboxPage', placeholder: 'Page title', required: false, tabIndex: 10, templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val() }); widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}') .data('oouiInfused', widget); let fieldset = new OO.ui.FieldsetLayout({ classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'], id: 'templatesandbox-editform', items: [ makeTemplateField(), new OO.ui.ActionFieldLayout( widget, new OO.ui.ButtonInputWidget({ id: 'wpTemplateSandboxPreview', name: 'wpTemplateSandboxPreview', label: 'Show preview', tabIndex: 10, type: 'submit', useInputTag: true }), { align: 'top' } ) ], label: 'Preview page with this template' }); fieldset.$label.append('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await $.get( `https://en.wikipedia.org/w/rest.php/v1/page/${encodeURIComponent(xfd.page)}/html` ))); await queryTitles(xfd, xfd.titleExtractor($page)); }; let queryCat = async (xfd, gcmcontinue) => { let response = await new mw.Api().get({ action: 'query', prop: 'info|categories', inprop: 'watched', clprop: 'sortkey', clcategories: `Category:${xfd.cat}`, generator: 'categorymembers', gcmtitle: `Category:${xfd.cat}`, gcmlimit: 'max', gcmsort: 'timestamp', gcmdir: 'older', gcmcontinue: gcmcontinue, formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched && p.categories?.[0]?.sortkeyprefix !== ' ') { xfd.pages.push(p.title); } }); if (response?.continue?.gcmcontinue) { await queryCat(xfd, response.continue.gcmcontinue); } }; let show = async (xfd, lastId, isCache) => { if (xfd.portlet && isCache) return; let portletId = 'p-xfdnotifier-' + xfd.id; if (xfd.portlet) { $(xfd.portlet).find('ul').empty(); if (!xfd.pages.length) return; } else { await $.ready; xfd.portlet = mw.util.addPortlet(portletId, xfd.label, '#' + lastId); } let $label = $(`#${portletId}-label`).attr('title', xfd.full); if (xfd.page) { $label.wrapInner($('<a>').attr('href', mw.util.getUrl(xfd.page))); } xfd.pages.forEach(p => { let t = mw.Title.newFromText(p); let isTalk = t.isTalkPage(); let $other = $('<a>').attr({ href: t[isTalk ? 'getSubjectPage' : 'getTalkPage']().getUrl(), title: isTalk ? 'subject' : 'talk' }).text(isTalk ? 's' : 't'); let link = mw.util.addPortletLink(portletId, t.getUrl(), p).querySelector('a'); $('<span>').addClass('xfdnotifier-sublinks').append( $('<span>').append($other), $('<span>').append( $('<a>').attr({ href: t.getUrl({ action: 'history' }), title: 'history' }).text('h') ) ).insertAfter(link); }); }; mw.hook('wikipage.content').add(mw.util.throttle(async () => { let cache = mw.storage.getObject('xfdnotifier') || {}; let lastId = 'p-tb'; for (let xfd of xfds) { let portletId = 'p-xfdnotifier-' + xfd.id; let now = Math.floor(Date.now() / 1000); if (now - cache[xfd.id]?.[0] < 600) { xfd.pages = cache[xfd.id].slice(1); await show(xfd, lastId, true); lastId = portletId; continue; } xfd.pages = []; if (xfd.cat) { await queryCat(xfd); } else if (xfd.page) { await queryPage(xfd); } cache[xfd.id] = [now, ...xfd.pages]; mw.storage.setObject('xfdnotifier', cache, 604800); await show(xfd, lastId); lastId = portletId; } }, 1800000)); })(); 5ike1ho30gg38q9deuqu7kozkfregps 739823 739822 2026-04-30T05:52:47Z Nardog 40946 739823 javascript text/javascript (async function listTools() { let pageAction = mw.config.get('wgAction'); let isView = pageAction === 'view'; let isEdit = ['edit', 'submit'].includes(pageAction); if (!isView && !isEdit) return; let pageType = mw.config.get('wgCanonicalSpecialPageName') || mw.config.get('wgNamespaceNumber'); if (isView && !pageType && !mw.config.exists('wgRedirectedFrom') && !mw.config.get('wgIsRedirect') && !mw.config.get('wgPageName').includes('/') ) { return; } await mw.loader.using([ 'mediawiki.util', 'mediawiki.Title', 'mediawiki.api', 'mediawiki.interface.helpers.styles' ]); mw.loader.addStyleTag(`.listtools:not(#mw-content-subtitle .listtools) { font-size: 85%; } .listtools, .listtools a { font-weight: normal !important; font-style: normal; } .mw-datatable .listtools { display: block; } .listtools + .mw-whatlinkshere-tools, #watchlist-edit-form .listtools ~ .mw-changeslist-links, .mw-special-DisambiguationPageLinks .listtools + a { display: none; }`); let messages = Object.assign({ watched: 'Added "$1" to your watchlist', watchFail: `Couldn't watch "$1"`, unwatchFail: `Couldn't unwatch "$1"` }, window.listtoolsMessages); let getMsg = (key, ...args) => ( Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key ); let notif; let watchHandler = async function (e) { e.preventDefault(); let $link = $(this); let $wrapper = $link.parent(); $link.detach(); let params = new URLSearchParams(this.search); let action = params.get('action'); $wrapper.text(getMsg(action + 'ing')); let pn = params.get('title').replaceAll('_', ' '); let promise = new mw.Api()[action](pn); if (notif) { notif.close(); notif = null; } try { let result = await promise; if (!result || !result[action + 'ed']) throw ''; let newAction = action === 'watch' ? 'unwatch' : 'watch'; params.set('action', newAction); $link.add(`.listtools-watch > a[href="${this.pathname + this.search}"]`) .attr('href', this.pathname + '?' + params) .text(getMsg(newAction)); if (action !== 'watch') return; let require = await mw.loader.using([ 'mediawiki.notification', 'mediawiki.watchstar.widgets' ]); notif = await mw.notify( new (require('mediawiki.watchstar.widgets'))('watch', pn, null, $.noop, { message: getMsg('watched', pn) }).$element, { tag: 'listtools' } ); } catch { notif = await mw.notify(getMsg(action + 'Fail', pn), { tag: 'listtools', type: 'error' }); } finally { $wrapper.html($link); } }; let extGetMain = function () { return this.title; }; let re = new RegExp(`(?:\\?title=|${ mw.util.escapeRegExp(mw.format(mw.config.get('wgArticlePath'), '')) })([^#&?]+)`); let processed = new WeakSet(); let processLinks = ($links, module, titles) => { let isBatch = !!titles; titles = titles || new Set(); $links.each(function (i) { if (processed.has(this)) return; let $link = $links.eq(i); let pn; if (module.useText) { pn = $link.text(); } else { let match = $link.attr('href')?.match(re); if (!match) return; pn = decodeURIComponent(match[1]); } let t = mw.Title.newFromText(pn); if (!t) return; if (module.titlesOnly) { let text = $link.text(); if (text !== pn.replaceAll('_', ' ') && (text !== t.getMainText() || t.namespace === 2) ) { return; } } if ($link.is('.external, .extiw')) { Object.assign(t, { getMain: extGetMain, host: this.host, namespace: 0, title: pn }); } else { if (t.namespace < 0) return; if ($link.hasClass('new')) { t.missing = true; } titles.add(t.getSubjectPage().toText()); } let $tools = $('<span>').addClass('listtools mw-changeslist-links') .data('listtools', t); tools.forEach(tool => { addTool($tools, tool); }); if ($link.is(':is(del, bdi) > :only-child')) { if (module.position === 'end') { $link.parent().parent().append(' ', $tools); } else { $link.parent().after(' ', $tools); } } else if (module.position === 'end') { $link.parent().append(' ', $tools); } else { $link.after(' ', $tools); } if (module.post) { module.post($tools); } processed.add(this); }); if (!isBatch) { getWatched(titles); } }; let tools = [ { name: 'edit', url: t => t.getUrl({ action: 'edit' }) }, { name: 'hist', url: t => !t.missing && t.getUrl({ action: 'history' }) }, { name: 'links', url: t => mw.util.getUrl('Special:WhatLinksHere/' + t) }, { name: 'watch', url: t => !t.host && t.getSubjectPage().getUrl({ action: 'watch' }), callback: watchHandler } ]; let addTool = ($tools, tool, escapedName) => { let t = $tools.data('listtools'); let $duplicate = escapedName && $tools.children('.listtools-' + escapedName); let url = tool.url; if (typeof url === 'function') { url = url(t); if (!url) { $duplicate?.remove(); return; } } let $link = $('<a>').attr('href', url).text(getMsg(tool.name)); if (t.host) { $link.prop('host', t.host); } if (tool.callback) { $link.on('click', tool.callback); } let $wrapper = $('<span>').addClass('listtools-' + tool.name) .append($link); let $next = tool.next && $tools.children('.listtools-' + tool.next); if ($next?.length) { $duplicate?.remove(); $next.before($wrapper); } else if ($duplicate?.length) { $duplicate.replaceWith($wrapper); } else { $tools.append($wrapper); } }; let extend = tool => { if (tool.label && !Object.hasOwn(messages, tool.label)) { messages[tool.name] = tool.label; } if (tool.next) { tool.next = $.escapeSelector(tool.next); } let existingTool = tools.find(t => t.name === tool.name); if (existingTool) { Object.assign(existingTool, tool); } else { tools.push(tool); } let escapedName = existingTool && $.escapeSelector(tool.name); let $allTools = $('.listtools'); $allTools.each(function (i) { addTool($allTools.eq(i), tool, escapedName); }); }; let getWatched = async titles => { if (!Array.isArray(titles)) { titles = [...titles].slice(0, 500); } if (!titles.length) return; (await new mw.Api().post({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages.forEach(page => { if (!page.watched) return; $(`.listtools-watch > a[href="${mw.util.getUrl(page.title, { action: 'watch' })}"]`) .attr('href', mw.util.getUrl(page.title, { action: 'unwatch' })) .text(getMsg('unwatch')); }); getWatched(titles.slice(50)); }; mw.hook('listtools.ready').fire(extend); let catTreeCallback = (records, observer) => { let $links = $(records[0].target).find('.CategoryTreeItem > bdi > a'); if ($links.length) { observer.takeRecords(); observer.disconnect(); processLinks($links, catTreeModule); } }; let catTreeModule = { selector: '.CategoryTreeItem > bdi > a', types: [14, 'CategoryTree'], position: 'end', post: $tools => { $tools.parent().next('.CategoryTreeChildren').each(function () { new MutationObserver(catTreeCallback) .observe(this, { childList: true }); }); } }; let modules = [ { selector: '#mw-pages li > a, #mw-pages li > span > a', types: [14] }, catTreeModule, { selector: '#mw-imagepage-section-linkstoimage a, #mw-imagepage-section-globalusage a', types: [6] }, { selector: '#mw-globalusage-result a', types: ['GlobalUsage'] }, { selector: '.mw-search-result-heading > a, .searchalttitle > a.mw-redirect, .iw-result__title > a, .mw-search-exists a', types: ['Search'] }, { selector: '.mw-search-createlink a', types: ['Search'], titlesOnly: true }, { selector: '#watchlist-edit-form .cdx-table td > label > a', types: ['EditWatchlist'] }, { selector: '.plainlinks > li > a', types: ['AbuseLog'], titlesOnly: true }, { selector: '#mw-allmessagestable td:first-child > a:first-child:not(.new)', types: ['Allmessages'], position: 'end' }, { selector: '.mw-spcontent li a', types: ['DisambiguationPageLinks', 'Listredirects'], titlesOnly: true }, { selector: 'li > a:first-child', types: ['FileDuplicateSearch'] }, { selector: '.TablePager_col_title > a:first-child, .TablePager_col_template > a', types: ['LintErrors'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: 'form > ul > li > a', types: ['Nuke'], position: 'end', titlesOnly: true }, { selector: '.page-assessments a', types: ['PageAssessments'], titlesOnly: true }, { selector: '.TablePager_col_pr_page > a', types: ['Protectedpages'], position: 'end' }, { selector: '#mw-content-text > ul a', types: ['Protectedtitles'], position: 'end' }, { selector: '.mw-fr-pending-changes-page-title', types: ['PendingChanges'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: '#mw-content-text > ul a:first-child', types: ['StablePages'], position: 'end' }, { selector: '.TablePager_col__page a', types: ['TopicSubscriptions'] }, { selector: '.undeleteResult > a', types: ['Undelete'], position: 'end', useText: true }, { selector: '.TablePager_col_img_name > a:first-child', // types: ['Listfiles'], position: 'end' }, { selector: '.mw-newpages-pagename', post: $tools => { let $nodes = $tools.parent().contents(); $nodes.slice( $nodes.index($tools) + 1, $nodes.index($nodes.filter('.mw-newpages-length')) ).replaceWith(' '); } }, { selector: '#mw-whatlinkshere-list li > bdi > a' }, { selector: '.mw-changeslist-log-entry > a:not(.mw-changeslist-log-gblblock a, .mw-changeslist-log-globalauth a)', titlesOnly: true }, { selector: '.mw-logevent-loglines > li:not(.mw-logline-gblblock, .mw-logline-globalauth) > a', types: ['Log'], titlesOnly: true }, { selector: '#mw-diff-otitle1 > strong > a, #mw-diff-ntitle1 > strong > a', types: ['ComparePages'], position: 'end' }, { selector: '#movepage-oldlink, #movepage-newlink', types: ['Movepage'] }, { selector: '.mw-undelete-revision a:not(.mw-userlink, .mw-usertoollinks > a)', types: ['Undelete'], useText: true }, { selector: '.galleryfilename, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a' }, { selector: '.mw-spcontent li a', position: 'end', titlesOnly: true } ]; if (isEdit) { let post = $tools => { if (!$tools[0].closest('.templatesUsed')) return; $tools.parent().contents().last().each(function () { this.textContent = this.textContent.slice(1); }).end().slice(-3, -1).remove(); }; let callback = mw.util.debounce(() => { processLinks( $('.mw-editfooter-list a, #wikiPreview > .previewnote a'), { titlesOnly: true, post } ); }, 500); mw.hook('wikipage.editform').add($form => { callback(); $form.find('.templatesUsed').each(function () { if (processed.has(this)) return; processed.add(this); new MutationObserver(callback) .observe(this, { childList: true, subtree: true }); }); }); } else if (typeof pageType === 'number') { $(() => { processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {}); }); } mw.hook('wikipage.content').add($content => { let titles = new Set(); let $links = $content.find('a'); modules.forEach(module => { if (module.types && !module.types.includes(pageType)) return; processLinks($links.filter(module.selector), module, titles); }); getWatched(titles); }); }()); mw.hook('listtools.ready').add(extend => { // extend({ // name: 'talk', // url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(), // next: 'hist' // }); extend({ name: 'subject', url: t => t.isTalkPage() && t.getSubjectPage().getUrl(), next: 'hist' }); extend({ name: 'last', url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }), next: 'links' }); // extend({ // name: 'purge', // url: t => t.getUrl({ action: 'purge' }), // next: 'watch', // callback: function (e) { // e.preventDefault(); // let $link = $(this); // let $wrapper = $link.parent(); // $link.detach(); // $wrapper.text('purging'); // let pn = $wrapper.closest('.listtools').data('listtools').toText(); // new mw.Api().post({ // action: 'purge', // forcelinkupdate: 1, // titles: pn, // formatversion: 2 // }).then(response => { // if (response.purge[0].purged) { // mw.notify(`Purged "${pn}"'`); // } // }).always(() => { // $wrapper.html($link); // }); // } // }); extend({ name: 'copy', url: '#', callback: function (e) { e.preventDefault(); let text = $(this).closest('.listtools').data('listtools').toText(); let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch (err) {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } } }); }); (mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') && mw.config.get('wgCanonicalSpecialPageName') !== 'GlobalContributions' && (function consecudiff() { mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}'); let isHist = mw.config.get('wgAction') === 'history'; class Consecudiff { constructor(lis, isContribs) { this.isContribs = isContribs; this.isEnhanced = !isHist && !isContribs && lis[0].classList.contains('mw-enhanced-rc'); this.threshold = isContribs ? window.consecudiffContribsThreshold || 120 : isHist ? window.consecudiffHistThreshold || 720 : window.consecudiffThreshold || 720; this.strictMode = !isContribs && !!window.consecudiffDetectInterruptions; this.diffSelector = isHist ? 'a.mw-history-histlinks-previous' : '.mw-changeslist-diff'; this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' || (isHist || isContribs) && 'a.mw-changeslist-date'; this.hybridSelector = this.diffSelector; if (this.permaSelector) { this.hybridSelector += ', ' + this.permaSelector; } this.topClass = isContribs ? 'mw-contributions-current' : 'mw-changeslist-last'; let dependencies = ['mediawiki.util']; if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') { dependencies.push('mediawiki.language.months'); } mw.loader.using(dependencies, () => { let chunks; if (isHist) { chunks = this.chunkByUser(lis); } else { chunks = []; this.groupByTitle(lis).forEach(group => { chunks.push(...this.chunkByUser(group)); }); } let subchunks = []; chunks.forEach(chunk => { subchunks.push(...this.divideByDate(chunk)); }); let linkPairs = []; subchunks.forEach(subchunk => { linkPairs.push(...this.makeLinks(subchunk)); }); linkPairs.forEach(([$span, parent]) => { $span.appendTo(parent); }); }); } groupByTitle(lis) { let selector = this.isContribs ? '.mw-contributions-title' : '.mw-changeslist-title'; let lisByTitle = {}; lis.forEach(li => { let link = (this.isEnhanced ? li.closest('table') : li) .querySelector(selector); if (!link) return; let title = link.textContent; if (!lisByTitle.hasOwnProperty(title)) { lisByTitle[title] = []; } lisByTitle[title].push(li); }); return Object.values(lisByTitle).filter(group => group.length > 1); } chunkByUser(lis) { if (this.isSingleContribs) { return [lis]; } let chunks = [], lastSplitAt = 0, prevUser; this.isSingleContribs = lis.some((li, i) => { let link = li.querySelector('.mw-userlink'); if (!link && this.isContribs) { return true; } let user = link && link.textContent; if (!link || i && user !== prevUser) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevUser = user; }); if (this.isSingleContribs) { return [lis]; } chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } divideByDate(lis) { let chunks = [], lastSplitAt = 0, prevDate; lis.forEach((li, i) => { let date; if (isHist || this.isContribs) { date = this.parseDate( li.querySelector('.mw-changeslist-date').textContent ); } else { date = Date.parse( li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z') ); } if (date) { date = date / 60000; } if (i && prevDate - date > this.threshold) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevDate = date; if (!this.strictMode || lastSplitAt === i) return; let prevDiff = lis[i - 1].querySelector(this.diffSelector); if (prevDiff) { let prevNext = mw.util.getParamValue('oldid', prevDiff.search); if (prevNext !== li.dataset.mwRevid) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } } }); chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } makeLinks(lis) { let count = lis.length; let firstPerma; let start = lis.findIndex(li => ( firstPerma = li.querySelector(this.hybridSelector) )); if (start === -1 || count - start < 2) return []; let end, lastDiff; for (let i = count - 1; i > start; i--) { if (!isHist && !this.isContribs) { lastDiff = lis[i].querySelector(this.diffSelector); if (lastDiff || lis[i].classList.contains('mw-changeslist-src-mw-new') ) { end = i + 1; break; } } if (this.permaSelector && lis[i].querySelector(this.permaSelector)) { end = i + 1; break; } } if (!end) return []; count = end - start; let params = { diff: lis[start].dataset.mwRevid }; if (lastDiff) { params.oldid = mw.util.getParamValue('oldid', lastDiff.search); } else { params.oldid = lis[end - 1].dataset.mwRevid; if (isHist && lis[end - 1].querySelector(this.diffSelector) || this.isContribs && !lis[end - 1].querySelector('.newpage') ) { params.direction = 'prev'; } } let title = !isHist && mw.util.getParamValue('title', firstPerma.search); let url = mw.util.getUrl(title, params); let classes = 'consecudiff'; if (!isHist && lis[start].classList.contains(this.topClass)) { classes += ' consecudiff-top'; } return lis.slice(start, end).map((li, i) => [ $('<span>').addClass(classes).append( $('<a>') .attr('href', url) .text(this.convertNumber(count - i + '/' + count)) ), this.isEnhanced ? li.tagName === 'TR' ? li.lastElementChild : li.querySelector('.mw-changeslist-line-inner') : li ]); } parseDate(s) { let date = Date.parse(s); if (date) { return date; } if (s.includes(',')) date = Date.parse(s.replace(',', '')); if (date) { return date; } if (mw.loader.getState('mediawiki.language.months') !== 'ready') return; s = s.replace(/\D/g, c => { let n = mw.language.convertNumber(c, true); return Number.isNaN(n) ? c : n; }); let h, m; s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => { h = $1; m = $2; return ' '; }); if (!h) return; let y, dateFirst; s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => { y = $2; dateFirst = /\d/.test($1); return $1 + ' '; }); if (!y) return; let mo, d; if (dateFirst) { [d, s] = this.getDate(s); if (!d) return; [mo, s] = this.getMonth(s); if (mo === -1) return; } else { [mo, s] = this.getMonth(s); if (mo === -1) return; [d, s] = this.getDate(s); if (!d) return; } return new Date(y, mo, d, h, m).getTime(); } getMonth(s) { if (!this.months) { this.months = mw.language.months.abbrev .concat(mw.language.months.names, mw.language.months.genitive) .reverse(); } let mo = this.months.findIndex(mn => { let temp = s.replace(mn, ' '); if (temp !== s) { s = temp; return true; } }); if (mo === -1) { let [numeric, temp] = this.getDate(s); numeric = parseInt(numeric); if (numeric > 0 && numeric < 13) { mo = numeric - 1; s = temp; } } else { mo = 11 - mo % 12; } return [mo, s]; } getDate(s) { let d; s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => { d = $2; return $1 + ' '; }); return [d, s]; } convertNumber(num) { try { return mw.language.convertNumber(num); } catch (e) { return num; } } } mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-body').each(function () { let lis = this.querySelectorAll('.mw-contributions-list > li'); if (lis.length > 1) { new Consecudiff([...lis], !isHist); } }); if (isHist) return; let $lists = $content.filter('.mw-changeslist'); if (!$lists.length) { $lists = $content.find('.mw-changeslist'); } $lists.each(function () { let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]'); if (lis.length > 1) { new Consecudiff([...lis]); } }); }); }()); if (mw.config.get('wgNamespaceNumber') === 14 && ( mw.config.get('wgAction') === 'view' || !mw.config.get('wgArticleId') )) { mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.UserInputWidget', 'mediawiki.widgets.datetime', 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement', 'mediawiki.interface.helpers.styles', 'user.options' ]); } $(function moveHistory() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Move history', 't-movehistory').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.moveHistoryDialog) { window.moveHistoryDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter', 'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions', 'mediawiki.interface.helpers.styles' ]); }); }); }); $(function sectionSearch() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Section search', 't-sectionsearch').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.sectionSearchDialog) { window.sectionSearchDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows', 'mediawiki.widgets', 'mediawiki.widgets.NamespacesMultiselectWidget' ]); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth' && mw.loader.using('jquery.tablesorter', function sortCentralAuthByEditCount() { mw.hook('wikipage.content').add($content => { let $table = $content.find('.mw-centralauth-wikislist').has('td'); if (!$table.length) return; $table.tablesorter().data('tablesorter').sort([{ 4: 'desc' }, { 1: 'asc' }]); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && [10, 828].includes(mw.config.get('wgNamespaceNumber')) && !mw.config.get('wgTitle').endsWith('/doc') && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoTestcases.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/TemplatePreviewGuard.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // $(function templatePreviewGuard() { // let button = document.querySelector('input[name="wpTemplateSandboxPreview"]'); // if (!button) return; // let proceed; // button.addEventListener('click', e => { // if (proceed) { // proceed = false; // return; // } // e.preventDefault(); // e.stopPropagation(); // let formData = new FormData(button.form); // let page = formData.get('wpTemplateSandboxPage'); // let temp = formData.get('wpTemplateSandboxTemplate'); // if (!page || !temp) return; // mw.loader.using('mediawiki.api').then(() => ( // new mw.Api().get({ // action: 'query', // titles: page, // prop: 'templates', // tltemplates: temp, // formatversion: 2 // }) // )).always(response => { // if (((((response || {}).query || {}).pages || [])[0] || {}).templates || // confirm(`"${page}" doesn't appear to transclude "${temp}". Continue?`) // ) { // proceed = true; // button.click(); // } // }); // }, true); // if (!mw.config.get('wgArticleId')) return; // let widgetEl = document.querySelector('#wpTemplateSandboxPage.oo-ui-widget'); // if (!widgetEl) return; // let pn = mw.config.get('wgPageName').replace(/_/g, ' '); // mw.loader.using(['mediawiki.api', 'oojs-ui-core']).then(() => ( // new mw.Api().get({ // action: 'query', // titles: pn, // prop: 'transcludedin', // tiprop: 'title', // tilimit: 'max', // formatversion: 2 // }) // )).then(response => { // if (!response.batchcomplete) return; // let pages = response.query.pages[0].transcludedin // .filter(o => o.title !== pn); // if (!pages.length) return; // let widget = OO.ui.infuse(widgetEl); // if (pages.length === 1) { // widget.setValue(pages[0].title); // return; // } // widget.$element.replaceWith( // new OO.ui.ComboBoxInputWidget({ // id: 'wpTemplateSandboxPage', // maxlength: widget.$input.prop('maxLength'), // name: widget.$input.prop('name'), // options: pages // .sort((a, b) => a.ns - b.ns || -(a.title < b.title)) // .map(o => ({ data: o.title })), // placeholder: widget.$input.prop('placeholder'), // tabIndex: widget.getTabIndex(), // value: widget.getValue() // }).on('enter', e => { // e.preventDefault(); // button.click(); // }).$element // ); // }); // }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(async () => { let form = document.getElementById('editform'); if (!form) return; let formData = new FormData(form); let section = formData.get('wpSection'); if (section === 'new') return; let widget = document.getElementById('wpSummaryWidget'); if (!widget) return; let isOld = formData.get('altBaseRevId') > 0 || (formData.get('baseRevId') || formData.get('parentRevId')) !== formData.get('editRevId'); await mw.loader.using([ 'jquery.textSelection', 'mediawiki.util', 'mediawiki.api', 'oojs-ui-core', 'oojs-ui.styles.icons-editing-core' ]); let $textarea = $('#wpTextbox1'); let input = OO.ui.infuse(widget); let button = new OO.ui.ButtonWidget({ framed: false, icon: 'undo', classes: ['autosectionlink-button'], invisibleLabel: true, label: 'Restore previous section link' }).toggle().on('click', () => { let cache = button.getData(); input.setValue(input.getValue().replace( /^(\/\*.*?\*\/)?\s*/, cache[0] ? '/* ' + cache[0] + ' */ ' : '' )); updatePreview(cache[0]); cache.reverse(); }).on('toggle', () => { input.$input.css('width', `calc(100% - ${button.$element.width()}px)`); }); input.$input.after(button.$element); let update = mw.util.debounce($diff => { let lines = $textarea.textSelection('getContents').trimEnd().split('\n'); let modLines = [...lines]; let firstLineNum; if (isOld) { let i, lastLineNum; $diff.find('td:last-child').each(function () { if (this.classList.contains('diff-lineno')) { i = this.textContent.replace(/\D+/g, '') - 1; } else if (this.classList.contains('diff-context')) { i++; } else if (this.classList.contains('diff-addedline')) { i++; if (!firstLineNum) { firstLineNum = i; } lastLineNum = i; } else if (this.classList.contains('diff-empty')) { if (!firstLineNum) { firstLineNum = i === 0 ? 1 : i; } lastLineNum = i; } }); modLines.length = lastLineNum || 0; } else { let origLines = $textarea.prop('defaultValue').trimEnd().split('\n'); firstLineNum = lines.findIndex((line, i) => line !== origLines[i]) + 1; if (!firstLineNum) { firstLineNum = lines.length < origLines.length ? lines.length : 1; } for (let i = 1, x = lines.length, y = origLines.length; (section ? i < x : i <= x) && lines[x - i] === origLines[y - i]; i++ ) { modLines.pop(); } } let re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/, lowest = 7; modLines.slice(firstLineNum).forEach(line => { let match = line.match(re); if (match?.[1].length < lowest) { lowest = match[1].length; } }); let head; modLines.slice(0, firstLineNum).reverse().some(line => { let match = line.match(re); if (match?.[1].length < lowest) { head = match[2]; return true; } }); if (head) { head = head .replace(/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>|<!--.*?-->|\[(?:https?:)?\/\/[^\s\[\]]+\s([^\]]+)\]/gi, '$1$2$3') .replace(/''(.+?)''/g, '$1') .trim(); } else if (modLines.length && section < 1 && lowest === 7 && ( section === '0' || lines.slice(modLines.length).some(line => re.test(line)) )) { head = ''; } let v = input.getValue(); let match = v.match(/^\/\*\s*(.+?)\s*\*\/\s*/); let prev = match?.[1]; if (prev === head) return; input.setValue(( head || head === '' ? '/* ' + head + ' */ ' : '' ) + (match ? v.slice(match[0].length) : v)); button.setData([prev, head]).toggle(true); updatePreview(head); }, 500); let updatePreview = head => { let $comment = $('.mw-summary-preview > .comment'); if (!$comment.length) return; let url; if (head) { url = mw.util.getUrl() + '#' + head.replace(/[\s_]+/g, '_'); } else if (head === '') { head = mw.messages.get('autocomment-top', '(top)'); url = mw.util.getUrl(); } let paren = [...mw.messages.get('parentheses', '($1)')][0]; let $nodes = $comment.contents(); let $ac = $nodes.eq(1); if ($nodes[0]?.textContent === paren && $ac.is('.autocomment:first-child')) { if (head) { $ac.children('a').attr('href', url).children('bdi').text(head); } else { if ($nodes[2]?.nodeType === 3) { $nodes[2].textContent = $nodes[2].textContent.trimStart(); } $ac.remove(); } } else if (head) { let rtl = document.body.classList.contains('sitedir-rtl'); $comment.prepend( paren, $('<span>').addClass('autocomment').append( $('<a>').attr({ href: url, title: mw.config.get('wgPageName').replaceAll('_', ' ') }).text(rtl ? '←' : '→').append( $('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(head) ), mw.messages.get('colon-separator', ': ') ) ); if ($nodes[0]?.nodeType === 3) { let text = $nodes[0].textContent; if (text.startsWith(paren)) { text = text.slice(paren.length); } $nodes[0].textContent = ' ' + text; } } }; if (isOld) { mw.hook('wikipage.diff').add(update); } else { $textarea.on('input', update); mw.hook('ext.CodeMirror.input').add(update); update(); } new mw.Api().loadMessagesIfMissing(['autocomment-top', 'colon-separator', 'parentheses']); mw.loader.addStyleTag('.autosectionlink-button{position:absolute;top:0;right:0;margin:0}'); }); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (function copyRevId() { let handler = function (e) { e.preventDefault(); let text = this.closest('.diff td')?.querySelector('[data-mw-revid]')?.dataset.mwRevid || this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!text) return; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); document.execCommand('copy'); $input.remove(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id') ) ); }); }()); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (() => { let handler = async function (e) { e.preventDefault(); let td = this.closest('.diff td'); let rev = td ? td.querySelector('[data-mw-revid]')?.dataset.mwRevid : this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!rev) { mw.notify(`Couldn't get the revision.`, { tag: 'markasunseen', type: 'error' }); return; } let pn = td ? new URLSearchParams([...td.querySelectorAll('a')].pop()?.search).get('title') : mw.config.get('wgPageName'); if (!pn) return; await mw.loader.using('mediawiki.api'); let result = (await new mw.Api().postWithEditToken({ action: 'setnotificationtimestamp', [td ? 'newerthanrevid' : 'torevid']: rev, titles: pn, formatversion: 2 })).setnotificationtimestamp?.[0]; if (Object.hasOwn(result, 'notificationtimestamp')) { mw.notify(`Marked revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'success' }); } else if (result?.notwatched) { mw.notify('This page is not on your watchlist.', { tag: 'markasunseen', type: 'warn' }); } else { mw.notify(`Couldn't mark revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions after this one as unseen' }).on('click', handler).text('unseen'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions since this one as unseen' }).on('click', handler).text('unseen') ) ); }); })(); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && ((mw.config.get('wgNamespaceNumber') % 2 || mw.config.get('wgNamespaceNumber') === 4) || (mw.config.get('wgWikiID') === 'metawiki' && mw.config.get('wgPageContentModel') === 'wikitext')) && mw.loader.using(['mediawiki.util', 'mediawiki.Title'], function copyUnsig() { let handler = function (e) { e.preventDefault(); let parent = this.closest('li, td'); let ts = parent.textContent.match(/\d\d:\d\d, \d\d? [A-Z][a-z]+ \d{4}/)?.[0]; if (!ts) return; let user = parent.querySelector('.mw-userlink').textContent; if (mw.util.isIPv6Address(user)) { user = user.toUpperCase(); } let temp = mw.util.isIPAddress(user) ? 'unsigned IP' : 'unsigned'; let text = `{{subst:${temp}|${user}|${ts}}}`; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').filter(function () { if (mw.config.get('wgWikiID') === 'metawiki') { return true; } let link = this.querySelector('strong > a') || this.parentElement.querySelector('#differences-prevlink, #differences-nextlink'); if (!link) return; let t = mw.Title.newFromText(mw.util.getParamValue('title', link.search)); return t.isTalkPage() || t.namespace === 4; }).append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig') ) ); }); }); // mw.config.get('wgAction') === 'history' && // mw.loader.using('mediawiki.util', function () { // mw.hook('wikipage.content').add($content => { // $content.find('a.mw-changeslist-date').after(function () { // return [ // ' (', // $('<a>').attr('href', mw.util.getUrl(null, { // action: 'edit', // oldid: this.closest('li').dataset.mwRevid // })).text('e'), // ')' // ]; // }); // }); // }); // ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && // mw.hook('wikipage.content').add($content => { // $content.find('.mw-changeslist-history').parent().after(function () { // return $('<span>').append( // $('<a>').attr( // 'href', // this.firstElementChild.getAttribute('href').slice(0, -7) + 'edit' // ).text('e') // ); // }); // }); if (screen.width < 500) { mw.loader.addStyleTag('@font-face{font-family:CharisW;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/017b2b2ad86e09d3c22b8cf0dfc78247/CharisSILRegular.ttf) format(truetype)} @font-face{font-family:CharisW;font-weight:700;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/6f5069ac6a300dad45383c952e92c573/CharisSILBold.ttf) format(truetype)} body .IPA{font-family:CharisW,sans-serif} .mw-highlight-lines > pre{width:120em}'); location.hash && $(() => { let target = document.querySelector(':target'); if (target?.getBoundingClientRect().top < 0) { target.scrollIntoView(); } }); } ['edit', 'submit'].includes(mw.config.get('wgAction')) && (mw.config.exists('wgCodeEditorCurrentLanguage') || mw.config.exists('cmMode') && mw.config.get('cmMode') !== 'mediawiki') && (function saveNEdit() { let notif; $(document.body).on('click', '#wpSave', async function (e) { if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey || e.originalEvent?.defaultPrevented ) { return; } e.preventDefault(); await mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'jquery.textSelection', 'oojs-ui-core' ]); let button = OO.ui.infuse(this.parentElement).setDisabled(true); let $textarea = $('#wpTextbox1'); let text = $textarea.textSelection('getContents'); let $summary = $('#wpSummary'); let formData = new FormData(this.form); let promise = new mw.Api().postWithEditToken({ action: 'edit', title: mw.config.get('wgPageName'), text: text, section: formData.get('wpSection') || undefined, summary: $summary.textSelection('getContents'), [$('#wpMinoredit').prop('checked') ? 'minor' : 'notminor']: 1, baserevid: formData.get('editRevId'), basetimestamp: formData.get('wpEdittime'), starttimestamp: formData.get('wpStarttime'), watchlist: $('#wpWatchthis').prop('checked') ? 'watch' : 'unwatch', watchlistexpiry: formData.get('wpWatchlistExpiry') || undefined, undo: formData.get('wpUndidRevision') || undefined, undoafter: formData.get('wpUndoAfter') || undefined, contentformat: formData.get('format'), contentmodel: formData.get('model'), assertuser: mw.config.get('wgUserName'), formatversion: 2 }); notif?.close(); notif = null; try { let response = await promise; if (response?.edit?.result !== 'Success') throw ''; $('#editform > input[name="wpUndidRevision"], #editform > input[name="wpUndoAfter"]').remove(); $textarea.data('origtext', text).prop('defaultValue', text); $summary.val($summary.prop('defaultValue')); if (mw.loader.getState('mediawiki.editRecovery.edit') === 'ready') { let storage = mw.loader.moduleRegistry['mediawiki.editRecovery.edit'].packageExports['storage.js']; storage.deleteData(mw.config.get('wgPageName')); storage.closeDatabase(); } notif = await mw.notify(response.edit.nochange ? 'No change' : [ document.createTextNode('Saved'), $('<p>').append( new OO.ui.ButtonWidget({ href: mw.util.getUrl(), target: '_blank', label: 'View' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { diff: response.edit.newrevid || 'cur', diffonly: 1 }), target: '_blank', label: 'Diff' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { action: 'history' }), target: '_blank', label: 'History' }).$element )[0] ], { tag: 'savenedit' }); } catch (error) { notif = await mw.notify(error?.error?.info || error || 'Save failed', { autoHideSeconds: 'long', tag: 'savenedit', type: 'error' }); } finally { button.setDisabled(); } }); }()); mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgAction') === 'view' && mw.config.get('wgCategories')?.some(c => c.endsWith(' actors') || c.endsWith(' actresses')) && $(() => { let n = $.escapeSelector(mw.config.get('wgTitle').replace(/ \(.+\)$/, '')); let $links = $(`.hatnote a[title$="${n} filmography"], .hatnote a[title*="${n} on "], .hatnote a[title*="${n} performances"]`); if (!$links.length) return; let titles = {}; $links = $links.filter(function () { let text = this.textContent; return !(titles[text] = Object.hasOwn(titles, text)); }); mw.notify( $links.length === 1 ? $links.clone() : $('<ul>').append($links.clone().wrap('<li>').parent()), { autoHideSeconds: 'long' } ); }); ['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(mw.config.get('wgCanonicalSpecialPageName')) && $.when($.ready, mw.loader.using([ 'user.options', 'mediawiki.util', 'mediawiki.api' ])).then(function rcMuter() { let os = mw.user.options.get('userjs-rcmuter'); let set = new Set(os && os.split('|')); let save = () => { let ns = [...set].join('|'); if (ns === mw.user.options.get('userjs-rcmuter')) return; new mw.Api().saveOption('userjs-rcmuter', ns); mw.user.options.set('userjs-rcmuter', ns); $edit.attr('data-rcmuter', set.size); }; mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}'); let $edit = $('<a>').attr({ class: 'rcmuter-edit', href: '#', 'data-rcmuter': set.size }).text('Edit muted').on('click', e => { e.preventDefault(); mw.loader.using([ 'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget' ]).then(() => OO.ui.getWindowManager().getWindow('message')).then(dialog => { let multiselect = new mw.widgets.UsersMultiselectWidget({ $overlay: dialog.$overlay, ipAllowed: true, selected: [...set] }).connect(dialog, { change: 'updateSize', reorder: 'updateSize' }); let instance = dialog.open({ message: $([ document.createTextNode('Muted users:'), multiselect.$element[0] ]), size: 'medium' }); instance.opened.then(() => { setTimeout(() => { multiselect.focus().menu.toggle(false); }); }); instance.closed.then(result => { if (!result || result.action !== 'accept') return; set = new Set(multiselect.getSelectedUsernames()); save(); mw.notify('Changes will take effect in next load.', { tag: 'rcmuter' }); }); }); }); let buttonsShown; let $toggleButtons = $('<a>').attr('href', '#').text('Show toggle buttons').on('click', function (e) { e.preventDefault(); if (buttonsShown) { mw.hook('wikipage.content').remove(addButtons); $('.rcmuter-toggle').remove(); this.textContent = 'Show toggle buttons'; } else { mw.hook('wikipage.content').add(addButtons); this.textContent = 'Hide toggle buttons'; } buttonsShown = !buttonsShown; }); let $toggle = $('<a>').attr({ class: 'rcmuter-togglemuted', href: '#' }).text('Show muted').on('click', function (e) { e.preventDefault(); this.textContent = document.body.classList.toggle('rcmuter-disabled') ? 'Hide muted' : 'Show muted'; }); let $toggleSpan = $('<span>').hide().append($toggle); mw.util.addSubtitle( $('<span>').addClass('mw-changeslist-links').append( $('<span>').append($edit), $('<span>').append($toggleButtons), $toggleSpan )[0] ); let toggle = function (e) { e.preventDefault(); let user = $(this) .closest('.mw-userlink ~ .mw-usertoollinks, .mw-changeslist-line-inner-userLink ~ .mw-changeslist-line-inner-userTalkLink') .prevAll('.mw-userlink, .mw-changeslist-line-inner-userLink') .last().text().trim(); if (!user) { mw.notify(`Can't retrieve the username.`, { tag: 'rcmuter', type: 'error' }); return; } let muting = this.parentElement.classList.toggle('rcmuter-unmute'); set[muting ? 'add' : 'delete'](user); save(); this.textContent = muting ? 'unmute' : 'mute'; mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, { tag: 'rcmuter' }); }; let addButtons = $content => { if (!$content.is('#mw-content-text, .mw-changeslist')) { $content = $('#mw-content-text'); if ($content.has('.rcmuter-toggle').length) return; } let $tools = $content.find('.mw-usertoollinks.mw-changeslist-links'); let $muted = $tools.filter('.rcmuter-muted *'); $tools.not($muted).append( $('<span>').addClass('rcmuter-toggle').append( $('<a>').attr('href', '#').text('mute').on('click', toggle) ) ); if (!$muted.length) return; $muted.append( $('<span>').addClass('rcmuter-toggle rcmuter-unmute').append( $('<a>').attr('href', '#').text('unmute').on('click', toggle) ) ); }; let mutedCount; let filter = function () { let muted = set.has(this.textContent); if (muted) mutedCount++; return muted; }; mw.hook('wikipage.content').add($content => { if (!$content.is('#mw-content-text, .mw-changeslist')) return; if (!set.size) { $toggleSpan.hide(); return; } mutedCount = 0; $content.find('.changedby > .mw-userlink:only-child') .filter(filter).closest('table').addClass('rcmuter-muted'); $content.find('.mw-userlink:not(.changedby > *, .comment *, .rcmuter-muted *)') .filter(filter).closest('.mw-changeslist-line, table').addClass('rcmuter-muted') .closest('table.mw-enhanced-rc').find('.changedby > .mw-userlink').filter(filter).addClass('rcmuter-muted'); $toggleSpan.toggle(!!mutedCount); $toggle.attr('data-rcmuter', mutedCount); }); }); location.hostname.endsWith('.wikipedia.org') && mw.config.get('wgNamespaceNumber') % 2 === 0 && // mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.util')).then(function refRenamer() { if (!document.getElementById('p-tb')) return; let messages = Object.assign({ portlet: 'RefRenamer', loading: 'Loading RefRenamer...' }, window.refrenamerMessages); let clicked; mw.util.addPortletLink('p-tb', '#', messages.portlet, 't-refrenamer').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.refRenamer) { window.refRenamer(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript'); mw.notify(messages.loading, { autoHideSeconds: 'long', tag: 'refrenamer' }); }); }); if (['edit', 'submit'].includes(mw.config.get('wgAction'))) { mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/ExpandContractions.js&action=raw&ctype=text/javascript', 's'); mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/Unpipe.js&action=raw&ctype=text/javascript', 's'); } mw.config.get('wgAction') !== 'history' && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopyCodeBlock.js&action=raw&ctype=text/javascript', 's'); mw.config.exists('wgDiffNewId') && mw.config.get('wgDiscussionToolsFeaturesEnabled') && (function () { let data = {}, clickHandler, autoClear, run; window.dtc = data; let highlight = revId => { let ids = data[revId]; if (!ids || !ids.length) return; mw.loader.moduleRegistry['ext.discussionTools.init'].packageExports['highlighter.js'] .highlightNewComments(mw.dt.pageThreads, true, ids); if (clickHandler) { $(document.body).off('click', clickHandler); return; } $._data(document.body, 'events').click.some(o => { if (String(o.handler).includes('highlighter.clearHighlightTargetComment(')) { $(document.body).off('click', o.handler); clickHandler = o.handler; return true; } }); $._data(window, 'events').popstate.some(o => { if (String(o.handler).includes('highlighter.highlightTargetComment(')) { $(window).off('popstate', o.handler); return true; } }); }; let scroll = revId => { let ids = data[revId]; if (!ids || !ids.length) return; let yToSpan = Object.fromEntries( ids.map(id => document.getElementById(id)).filter(Boolean) .map(span => [span.getBoundingClientRect().y, span]) ); let ys = Object.keys(yToSpan); if (!ys.length) return; let lower = ys.filter(y => y > 10); if (!lower.length || Math.max(...lower) < document.documentElement.clientHeight ) { yToSpan[Math.min(...ys)].scrollIntoView(); } else { yToSpan[Math.min(...lower)].scrollIntoView(); } }; let scrollToNext = function (e) { e.preventDefault(); let revId = mw.config.get('wgDiffOldId'); if (!revId || !data[revId]) return; let i = data[revId].indexOf( this.closest('[data-mw-thread-id]').dataset.mwThreadId ); if (i === -1) return; let next = data[revId][i + 1] || data[revId][0]; document.getElementById(next).scrollIntoView(); }; mw.hook('wikipage.content').add(async $content => { let revId = mw.config.get('wgDiffOldId'); if (!revId) return; let param = new URLSearchParams(location.search).get('diffonly'); if (param && param !== '0') return; if (data[revId]) { highlight(revId); return; } await mw.loader.using(['ext.discussionTools.init', 'mediawiki.util']); let begin = Date.parse($('#mw-diff-otitle1 .mw-diff-timestamp').data('timestamp')); data[revId] = mw.dt.pageThreads.getCommentItems() .filter(c => c.timestamp > begin).map(c => c.id); if (!data[revId].length) return; await new Promise(setTimeout); highlight(revId); $content.find('.ext-discussiontools-init-replylink-buttons').filter(function () { return data[revId].includes(this.dataset.mwThreadId); }).children('span:last-of-type').before( ' | ', $('<a>').attr({ href: '#', role: 'button' }).text('next').on('click', scrollToNext) ); if (run || !document.getElementById('p-tb')) return; run = true; let portlet = mw.util.addPortletLink('p-tb', '#', 'Scroll to next', 't-scrolltonext'); portlet.firstElementChild.addEventListener('click', e => { e.preventDefault(); scroll(mw.config.get('wgDiffOldId')); }); mw.util.addPortletLink('p-tb', '#', 'Toggle highlight', 't-togglehighlight').firstElementChild.addEventListener('click', e => { e.preventDefault(); autoClear = !autoClear; if (autoClear) { $(document.body).on('click', clickHandler)[0].click(); } else { highlight(mw.config.get('wgDiffOldId')); } }); mw.loader.addStyleTag(`#t-scrolltonext{position:fixed;bottom:${portlet.clientHeight}px} #t-togglehighlight{position:fixed;bottom:0}`); }); }()); mw.config.get('wgNamespaceNumber') === 6 && mw.config.get('wgAction') === 'view' && mw.hook('wikipage.content').add($content => { $content.find('.filehistory .mw-usertoollinks-contribs').after(function () { return [ ' | ', $('<a>').attr('href', `${ mw.config.get('wgScript') }?title=Special:ListFiles/${ this.pathname.replace(/^.+\//, '') }&ilshowall=1`).text('uploads') ]; }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(function () { if (!$('input[name="wpSection"]').val()) return; mw.hook('wikipage.content').add(async $content => { let $refs = $content.find('.mw-ext-cite-warning-sectionpreview_no_text'); if (!$refs.length) return; let ids = {}; $refs.each(function () { ids[this.closest('[id]').id.replace(/-\d+$/, '')] = this; }); let response = await $.get(`/api/rest_v1/page/html/${encodeURIComponent(mw.config.get('wgPageName'))}`); $($.parseHTML(response)).find('.mw-reference-text').each(function () { ids[this.id.replace(/^mw-reference-text-|-\d+$/g, '')]?.replaceWith(this); }); }); }); mw.hook('moremenu.ready').add(config => { $('#mm-page-purge-cache > a').on('click', e => { e.preventDefault(); new mw.Api().post({ action: 'purge', forcelinkupdate: 1, titles: config.page.name, formatversion: 2 }).then(() => { location.href = mw.util.getUrl(); }); }); $('#mm-page-search-search-history-wikiblame > a').on('click', function (e) { e.preventDefault(); let q = prompt(); if (q === null) return; let href = this.href; if (q) { let removal = q[0] === '!'; if (removal) { q = q.slice(1); } href += '&needle=' + encodeURIComponent(q); if (removal) { href += '&binary_search_inverse=on'; } href += '&force_wikitags=on'; } open(href, '_blank'); }); $('#mm-page-expand-templates > a').on('click auxclick', function (e) { if (e.which > 2) return; e.preventDefault(); let revId = mw.config.get('wgRevisionId') || Number($('input[name=oldid]').val()); let url = revId ? '/w/rest.php/v1/revision/' + revId : '/w/rest.php/v1/page/' + config.page.encodedName; $.get(url).then(response => { $('<form>').attr({ method: 'post', action: this.href, target: '_blank' }).append( [ ['wpInput', response.source], ['wpContextTitle', config.page.name], ['wpRemoveComments', 1] ].map(([n, v]) => $('<input>').attr({ name: n, type: 'hidden' }).val(v)) ).appendTo(document.body).trigger('submit').remove(); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'ApiSandbox' && mw.hook('apisandbox.formatRequest').add((...args) => { args[4].complete = function () { setTimeout(() => { mw.hook('wikipage.content').fire($('.oo-ui-pageLayout-active')); }, 100); }; }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.storage')).then(async () => { let infuseAndCall = (query, method, ...args) => { let $widget = $(query); if ($widget.length) { return OO.ui.infuse($widget)[method](...args); } }; let section = $('input[name="wpSection"]').val(); if (section) { let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let save = () => { let newSource = $textarea.textSelection('getContents'); if (newSource === source) { mw.storage.session.remove('editfullpage'); } else { mw.storage.session.setObject('editfullpage', [ mw.config.get('wgPageName'), section, newSource.trimEnd(), infuseAndCall('#wpSummaryWidget', 'getValue') || '', Number(infuseAndCall('#wpMinoreditWidget', 'isSelected')) || 0, Number(infuseAndCall('#wpWatchthisWidget', 'isSelected')) || 0, infuseAndCall('#wpWatchlistExpiryWidget', 'getValue') || 'infinite' ]); } }; await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); setInterval(() => { mw.requestIdleCallback(save); }, 3000); window.addEventListener('beforeunload', save); return; } let data = mw.storage.session.getObject('editfullpage'); mw.storage.session.remove('editfullpage'); console.log(data); if (!data || data[0] !== mw.config.get('wgPageName')) return; let isNew = data[1] === 'new'; let isLead = data[1] === '0'; let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let newSource, start, msg, notifOpts = { autoHideSeconds: 'long' }; let orig = []; if (isNew) { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); newSource = source + (data[3] ? '\n== ' + data[3] + ' ==\n\n' : '\n') + data[2] + '\n'; start = source.length; } else { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core', 'mediawiki.api']); let { parse } = await new mw.Api().get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'sections', wrapoutputclass: '', disablelimitreport: 1, disableeditsection: 1, disabletoc: 1, formatversion: 2 }); let target = !isLead && parse.sections.find(s => s.index === data[1]); if (isLead || target) { let next = parse.sections.find(s => s.index - 1 === Number(data[1])); newSource = (isLead ? '' : [...source].slice(0, target.byteoffset)).join('') + data[2] + (next ? '\n\n' + [...source].slice(next.byteoffset).join('') : '\n'); start = isLead ? 0 : target.byteoffset; } else { newSource = source + '\n\n' + data[2] + '\n'; start = source.length; msg = `Section restored. Couldn't find the section. The source is appended at bottom.`; notifOpts.type = 'warn'; } orig[0] = infuseAndCall('#wpSummaryWidget', 'getValue'); infuseAndCall('#wpSummaryWidget', 'setValue', data[3]); } $textarea.textSelection('setContents', newSource); orig[1] = infuseAndCall('#wpMinoreditWidget', 'getSelected'); infuseAndCall('#wpMinoreditWidget', 'setSelected', data[4]); orig[2] = infuseAndCall('#wpWatchthisWidget', 'getSelected'); infuseAndCall('#wpWatchthisWidget', 'setSelected', data[5]); orig[3] = infuseAndCall('#wpWatchlistExpiryWidget', 'getValue'); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', data[6]); setTimeout(() => { $textarea.textSelection('setSelection', { start }); }); let notif = await mw.notify($([ document.createTextNode(msg || 'Section restored.'), $('<p>').append( new OO.ui.ButtonWidget({ flags: 'destructive', label: 'Discard' }).on('click', () => { $textarea.textSelection('setContents', source); if (orig[0]) { infuseAndCall('#wpSummaryWidget', 'setValue', orig[0]); } infuseAndCall('#wpMinoreditWidget', 'setSelected', orig[1]); infuseAndCall('#wpWatchthisWidget', 'setSelected', orig[2]); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', orig[3]); notif.close(); }).$element )[0] ]), notifOpts); }); mw.config.exists('wgPostEdit') && mw.loader.using('mediawiki.storage', () => { mw.storage.session.remove('editfullpage'); }); mw.config.get('wgAction') === 'history' && mw.hook('wikipage.content').add(async $content => { if (!$content.has('.mw-history-line-updated').length) return; let href = $content.find('a.mw-history-histlinks-current:not(.mw-history-line-updated a)').attr('href'); if (!href) { await mw.loader.using(['mediawiki.api', 'mediawiki.util']); let page = (await new mw.Api().get({ action: 'query', titles: mw.config.get('wgPageName'), prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev >= page.lastrevid) return; href = mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); } $content.find('.mw-history-compareselectedversions-button').first().after( ' ', $('<a>').attr({ class: 'unseendiff', href: href }).text('unseen') ); }); (async () => { let cspn = mw.config.get('wgCanonicalSpecialPageName'); let isBp = cspn === 'Blankpage'; if (!isBp && cspn !== 'Watchlist') return; await mw.loader.using('mediawiki.util'); let notify = async (text, options, pn) => { let msg = [document.createTextNode(text)]; if (pn) { msg.push( $('<p>').append( $('<a>').attr('href', mw.util.getUrl(pn)).text(pn), ' ', $('<span>').addClass('mw-changeslist-links').append( $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'edit' })) .text('edit') ), $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'history' })) .text('history') ) ) )[0] ); } if (isBp) { await $.ready; $('#mw-content-text').html(msg); } else { return mw.notify(msg, Object.assign(options || {}, { tag: 'unseendiff' })); } }; let getUrl = async pn => { await mw.loader.using('mediawiki.api'); let page = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; if (!page.notificationtimestamp) { notify(`Couldn't get the last seen time.`, { type: 'warn' }, pn); return; } let rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (rev === page.lastrevid) { notify('Already seen.', { type: 'warn' }, pn); return; } if (!rev) { rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, rvdir: 'newer', formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev) { notify(`Couldn't get the last seen revision.`, { type: 'warn' }, pn); return; } } if (rev > page.lastrevid) { notify(`Invalid rev for "${pn}" (rev: ${rev}, lastrevid: ${page.lastrevid})`, { autoHideSeconds: 'long', type: 'warn' }, pn); return; } return mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); }; if (isBp) { let pn = mw.config.get('wgTitle').match(/^[^/]+\/unseendiff\/(.+)$/)?.[1]; if (!pn) return; notify('Loading...', null, pn); let href = await getUrl(pn); if (!href) return; notify('Redirecting...', null, pn); location.href = href; return; } let handler = async function (e) { if (e.which > 2) return; e.preventDefault(); let pn = this.dataset.pn; if (!pn) { notify(`Couldn't get the page name.`, { type: 'error' }); return; } let notifPromise = notify('Loading...', { autoHideSeconds: 'long' }); let href = await getUrl(pn); if (!href) return; $(`.unseendiff-loader[data-pn="${$.escapeSelector(pn)}"]`).attr({ class: 'unseendiff', href: href, target: '_blank' }).off('click auxclick', handler); if (e.type === 'auxclick' || e.ctrlKey || e.metaKey || e.shiftKey) { open(href); } else { this.click(); } (await notifPromise).close(); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-changeslist-src-mw-edit.mw-changeslist-watchedunseen:not(.mw-changeslist-watchedseen) .mw-changeslist-line-inner' ).each(function () { let pn = this.dataset.targetPage || this.closest('[data-target-page]')?.dataset.targetPage || this.closest('table.mw-enhanced-rc')?.querySelector('[data-target-page]')?.dataset.targetPage; if (!pn) return; $('<span>').append( $('<a>').attr({ class: 'unseendiff-loader', href: mw.util.getUrl(`Special:BlankPage/unseendiff/${pn}`), 'data-pn': pn }).on('click auxclick', handler).text('unseen') ).appendTo( [...this.querySelectorAll('.mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); }); })(); ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.using('mediawiki.util', () => { let watched = new Set(); let query = async lis => { let titles = Object.keys(lis).slice(0, 50); if (!titles.length) return; await mw.loader.using('mediawiki.api'); let pages = (await new mw.Api().post({ action: 'query', titles: titles, prop: 'info', inprop: 'notificationtimestamp|watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages; for (let page of pages) { if (!Object.hasOwn(lis, page.title)) continue; if (page.watched) { watched.add(page); $(lis[page.title]).addClass('watched'); } if (!page.notificationtimestamp) continue; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev === page.lastrevid) continue; if (rev > page.lastrevid) { mw.notify($([ document.createTextNode('Invalid rev for "'), $('<a>').attr({ href: mw.util.getUrl(page.title, { action: 'history' }), target: '_blank' }).text(page.title)[0], document.createTextNode(`" (rev: ${rev}, lastrevid: ${page.lastrevid})`), ]), { autoHideSeconds: 'long', type: 'warn' }); continue; } $('<span>').append( $('<a>').attr({ class: 'unseendiff', href: mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }) }).text('unseen') ).appendTo( lis[page.title].map(li => ( [...li.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(li).before(' ') )) ); } titles.forEach(title => { delete lis[title]; }); query(lis); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-contributions-list > li:not(.mw-contributions-current)[data-mw-revid]' ).each(function () { let link = this.querySelector('a.mw-changeslist-date, a.mw-changeslist-history'); let pn = link ? new URLSearchParams(link.search).get('title') : ''; $('<span>').append( $('<a>').attr({ class: 'mw-changeslist-diff', href: mw.util.getUrl(pn, { diff: 'cur', oldid: this.dataset.mwRevid }) }).text('cur') ).appendTo( [...this.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); if (mw.config.get('wgWikiID') === 'wikidatawiki') return; let lis = {}; $content.find('.mw-contributions-title').each(function () { let title = this.textContent; if (!Object.hasOwn(lis, title)) { lis[title] = []; } lis[title].push(this.closest('li')); }); Object.keys(lis).forEach(title => { if (watched.has(title)) { $(lis[title]).addClass('watched'); delete lis[title]; } }); query(lis); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox9.js&action=raw&ctype=text/javascript'); mw.config.get('wgWikiID') === 'metawiki' && (async () => { let css = mw.loader.addStyleTag(`.wishtitle { font-size: 90%; font-style: italic; word-break: break-word; } .wishtitle > a { color: var(--color-warning, #886425); } .wishtitle > a:visited { color: var(--border-color-warning--hover, #735421); } .wishtitle-declined > a { text-decoration: line-through; } .wishtitle-declined > a:hover, .wishtitle-declined > a:focus, .mw-underline-always .wishtitle-declined > a { text-decoration: line-through underline; } #watchlist-edit-form .wishtitle { display: inline-block; } .mw-search-result-heading > .wishtitle, .catchangesviewer-table .wishtitle { display: block; } .catchangesviewer-table:has(.wishtitle) { white-space: wrap; }`); let lang = mw.config.get('wgUserLanguage'); let titles; let loadTitles = async () => { await mw.loader.using('mediawiki.storage'); titles = titles || mw.storage.getObject('wishtitles'); if (titles?.lang !== lang) { titles = { lang, w: [], fa: [] }; } }; let updateTitles = async (crwstatuses, crwcontinue) => { await mw.loader.using('mediawiki.api'); let params = { action: 'query', list: 'communityrequests-wishes', crwlang: lang, crwstatuses: crwstatuses, crwprop: 'title|updated', crwsort: 'updated', crwdir: 'ascending', crwlimit: 'max', crwcontinue: crwcontinue, formatversion: 2 }; if (!crwcontinue && !crwstatuses && titles._) { params.crwcontinue = `|${titles._}|0`; } let response = await new mw.Api().get(params); let wishes = response?.query?.['communityrequests-wishes']; if (wishes?.length) { let $span = $('<span>'); wishes.forEach(w => { let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1]; if (!id) return; titles.w[id - 1] = $span.html(w.title).text(); if (crwstatuses === 'declined') { (titles.wd = titles.wd || []).push(id - 1); } let faId = w.crfatitle?.match(/^Community Wishlist\/FA(\d+)/)?.[1]; if (!faId) return; titles.fa[faId - 1] = w.focusareatitle; }); if (!crwstatuses) { titles._ = wishes.at(-1).updated.replace(/\D/g, ''); } } let expiry = 86400; if (crwstatuses || crwcontinue) { let prev = mw.storage.getObject('_EXPIRY_wishtitles'); if (prev) { expiry = Math.round(Date.now() / 1000) + 86400 - prev; } } mw.storage.setObject('wishtitles', titles, expiry); crwcontinue = response?.continue?.crwcontinue; if (crwcontinue) { await updateTitles(crwstatuses, crwcontinue); } }; let getTitle = id => ( id[0] === 'W' ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1] ); let renderTitle = (title, id, tag = 'span') => { let classes = 'wishtitle'; if (id[0] === 'W' && titles.wd?.includes(id.slice(1) - 1)) { classes += ' wishtitle-declined'; } return $(`<${tag}>`).addClass(classes).append( $('<a>').attr({ href: `/wiki/Community_Wishlist/${id}`, title: `Community Wishlist/${id}` }).text(title) ); }; let callback = ([id, links]) => { let title = getTitle(id); if (!title) { return true; } $(links).after(' ', renderTitle(title, id)); }; let selector = '.mw-changeslist-title, ' + '.mw-changeslist-log-entry > a:not(.mw-userlink), ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' + '#watchlist-edit-form .cdx-table td > label > a, ' + '.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' + '.mw-contributions-title, ' + '#mw-whatlinkshere-list li > bdi > a, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-logevent-loglines > li > a, ' + '#mw-pages li > a, ' + '.catchangesviewer-table td:nth-child(3) > a'; mw.hook('wikipage.content').add(async $content => { let links = {}; $content.find('a').each(function () { if (!this.matches(selector)) return; let id = this.textContent.match( /^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/ )?.[1]; if (!id) return; (links[id] = links[id] || []).push(this); }); links = Object.entries(links); if (!links.length) return; await loadTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles('declined'); links.forEach(callback); }); let pn = mw.config.get('wgRelevantPageName'); let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1]; if (!id) return; await $.ready; let extTitle = document.querySelector('.ext-communityrequests-wish--title'); if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return; await loadTitles(); let title = getTitle(id); if (!title) { await updateTitles(); title = getTitle(id); if (!title) { await updateTitles('declined'); title = getTitle(id); if (!title) return; } } let $title = renderTitle(title, id, 'div'); if (mw.config.get('skin') === 'vector-2022') { $title.prependTo('.vector-page-toolbar'); } else { $title.insertAfter('#firstHeading'); } css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}'; if (extTitle) return; document.title = document.title.replace( pn.replaceAll('_', ' '), `${pn.replace(`Community_Wishlist/${id}`, title)} ($&)` ); })(); mw.config.get('wgWikiID') === 'metawiki' && mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => { if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return; let title = mw.config.get('wgTitle'); if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return; if (isWatched) { await new mw.Api().watch(title + '/Votes', expiry); mw.notify('Watching /Votes too.'); } else { await new mw.Api().unwatch(title + '/Votes'); mw.notify('Unwatched /Votes too.'); } }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && $(async () => { let $input = $('#wpTemplateSandboxTemplate'); if (!$input.length) return; mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}'); let makeTemplateField = () => new OO.ui.FieldLayout( new mw.widgets.TitleInputWidget({ inputId: 'wpTemplateSandboxTemplate', name: 'wpTemplateSandboxTemplate', showMissing: false, value: $input.val() }), { label: 'Template name:' } ); if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') { await mw.loader.using('mediawiki.widgets'); $input.parent().replaceWith(makeTemplateField().$element); return; } let require = await mw.loader.using([ 'ext.TemplateSandbox.TemplateSandboxTitleWidget', 'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options' ]); let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({ $overlay: true, id: 'wpTemplateSandboxPage', maxLength: 255, name: 'wpTemplateSandboxPage', placeholder: 'Page title', required: false, tabIndex: 10, templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val() }); widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}') .data('oouiInfused', widget); let fieldset = new OO.ui.FieldsetLayout({ classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'], id: 'templatesandbox-editform', items: [ makeTemplateField(), new OO.ui.ActionFieldLayout( widget, new OO.ui.ButtonInputWidget({ id: 'wpTemplateSandboxPreview', name: 'wpTemplateSandboxPreview', label: 'Show preview', tabIndex: 10, type: 'submit', useInputTag: true }), { align: 'top' } ) ], label: 'Preview page with this template' }); fieldset.$label.append('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await $.get( `https://en.wikipedia.org/w/rest.php/v1/page/${encodeURIComponent(xfd.page)}/html` ))); await queryTitles(xfd, xfd.titleExtractor($page)); }; let queryCat = async (xfd, gcmcontinue) => { let response = await new mw.Api().get({ action: 'query', prop: 'info|categories', inprop: 'watched', clprop: 'sortkey', clcategories: `Category:${xfd.cat}`, generator: 'categorymembers', gcmtitle: `Category:${xfd.cat}`, gcmlimit: 'max', gcmsort: 'timestamp', gcmdir: 'older', gcmcontinue: gcmcontinue, formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched && p.categories?.[0]?.sortkeyprefix !== ' ') { xfd.pages.push(p.title); } }); if (response?.continue?.gcmcontinue) { await queryCat(xfd, response.continue.gcmcontinue); } }; let show = async (xfd, lastId, isCache) => { if (xfd.portlet && isCache) return; let portletId = 'p-xfdnotifier-' + xfd.id; if (xfd.portlet) { $(xfd.portlet).find('ul').empty(); if (!xfd.pages.length) return; } else { await $.ready; xfd.portlet = mw.util.addPortlet(portletId, xfd.label, '#' + lastId); } let $label = $(`#${portletId}-label`).attr('title', xfd.full); if (xfd.page) { $label.wrapInner($('<a>').attr('href', mw.util.getUrl(xfd.page))); } xfd.pages.forEach(p => { let t = mw.Title.newFromText(p); let isTalk = t.isTalkPage(); let $other = $('<a>').attr({ href: t[isTalk ? 'getSubjectPage' : 'getTalkPage']().getUrl(), title: isTalk ? 'subject' : 'talk' }).text(isTalk ? 's' : 't'); let link = mw.util.addPortletLink(portletId, t.getUrl(), p).querySelector('a'); $('<span>').addClass('xfdnotifier-sublinks').append( $('<span>').append($other), $('<span>').append( $('<a>').attr({ href: t.getUrl({ action: 'history' }), title: 'history' }).text('h') ) ).insertAfter(link); }); }; mw.hook('wikipage.content').add(mw.util.throttle(async () => { let cache = mw.storage.getObject('xfdnotifier') || {}; let lastId = 'p-tb'; for (let xfd of xfds) { let portletId = 'p-xfdnotifier-' + xfd.id; let now = Math.floor(Date.now() / 1000); if (now - cache[xfd.id]?.[0] < 600) { xfd.pages = cache[xfd.id].slice(1); await show(xfd, lastId, true); lastId = portletId; continue; } xfd.pages = []; if (xfd.cat) { await queryCat(xfd); } else if (xfd.page) { await queryPage(xfd); } cache[xfd.id] = [now, ...xfd.pages]; mw.storage.setObject('xfdnotifier', cache, 604800); await show(xfd, lastId); lastId = portletId; } }, 1800000)); })(); qamjsejd4c7mp693ttrr6ed3nnm25bv 739827 739823 2026-04-30T06:01:25Z Nardog 40946 739827 javascript text/javascript (async function listTools() { let pageAction = mw.config.get('wgAction'); let isView = pageAction === 'view'; let isEdit = ['edit', 'submit'].includes(pageAction); if (!isView && !isEdit) return; let pageType = mw.config.get('wgCanonicalSpecialPageName') || mw.config.get('wgNamespaceNumber'); if (isView && !pageType && !mw.config.exists('wgRedirectedFrom') && !mw.config.get('wgIsRedirect') && !mw.config.get('wgPageName').includes('/') ) { return; } await mw.loader.using([ 'mediawiki.util', 'mediawiki.Title', 'mediawiki.api', 'mediawiki.interface.helpers.styles' ]); mw.loader.addStyleTag(`.listtools:not(#mw-content-subtitle .listtools) { font-size: 85%; } .listtools, .listtools a { font-weight: normal !important; font-style: normal; } .mw-datatable .listtools { display: block; } .listtools + .mw-whatlinkshere-tools, #watchlist-edit-form .listtools ~ .mw-changeslist-links, .mw-special-DisambiguationPageLinks .listtools + a { display: none; }`); let messages = Object.assign({ watched: 'Added "$1" to your watchlist', watchFail: `Couldn't watch "$1"`, unwatchFail: `Couldn't unwatch "$1"` }, window.listtoolsMessages); let getMsg = (key, ...args) => ( Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key ); let notif; let watchHandler = async function (e) { e.preventDefault(); let $link = $(this); let $wrapper = $link.parent(); $link.detach(); let params = new URLSearchParams(this.search); let action = params.get('action'); $wrapper.text(getMsg(action + 'ing')); let pn = params.get('title').replaceAll('_', ' '); let promise = new mw.Api()[action](pn); if (notif) { notif.close(); notif = null; } try { let result = await promise; if (!result || !result[action + 'ed']) throw ''; let newAction = action === 'watch' ? 'unwatch' : 'watch'; params.set('action', newAction); $link.add(`.listtools-watch > a[href="${this.pathname + this.search}"]`) .attr('href', this.pathname + '?' + params) .text(getMsg(newAction)); if (action !== 'watch') return; let require = await mw.loader.using([ 'mediawiki.notification', 'mediawiki.watchstar.widgets' ]); notif = await mw.notify( new (require('mediawiki.watchstar.widgets'))('watch', pn, null, $.noop, { message: getMsg('watched', pn) }).$element, { tag: 'listtools' } ); } catch { notif = await mw.notify(getMsg(action + 'Fail', pn), { tag: 'listtools', type: 'error' }); } finally { $wrapper.html($link); } }; let extGetMain = function () { return this.title; }; let re = new RegExp(`(?:\\?title=|${ mw.util.escapeRegExp(mw.format(mw.config.get('wgArticlePath'), '')) })([^#&?]+)`); let processed = new WeakSet(); let processLinks = ($links, module, titles) => { let isBatch = !!titles; titles = titles || new Set(); $links.each(function (i) { if (processed.has(this)) return; let $link = $links.eq(i); let pn; if (module.useText) { pn = $link.text(); } else { let match = $link.attr('href')?.match(re); if (!match) return; pn = decodeURIComponent(match[1]); } let t = mw.Title.newFromText(pn); if (!t) return; if (module.titlesOnly) { let text = $link.text(); if (text !== pn.replaceAll('_', ' ') && (text !== t.getMainText() || t.namespace === 2) ) { return; } } if ($link.is('.external, .extiw')) { Object.assign(t, { getMain: extGetMain, host: this.host, namespace: 0, title: pn }); } else { if (t.namespace < 0) return; if ($link.hasClass('new')) { t.missing = true; } titles.add(t.getSubjectPage().toText()); } let $tools = $('<span>').addClass('listtools mw-changeslist-links') .data('listtools', t); tools.forEach(tool => { addTool($tools, tool); }); if ($link.is(':is(del, bdi) > :only-child')) { if (module.position === 'end') { $link.parent().parent().append(' ', $tools); } else { $link.parent().after(' ', $tools); } } else if (module.position === 'end') { $link.parent().append(' ', $tools); } else { $link.after(' ', $tools); } if (module.post) { module.post($tools); } processed.add(this); }); if (!isBatch) { getWatched(titles); } }; let tools = [ { name: 'edit', url: t => t.getUrl({ action: 'edit' }) }, { name: 'hist', url: t => !t.missing && t.getUrl({ action: 'history' }) }, { name: 'links', url: t => mw.util.getUrl('Special:WhatLinksHere/' + t) }, { name: 'watch', url: t => !t.host && t.getSubjectPage().getUrl({ action: 'watch' }), callback: watchHandler } ]; let addTool = ($tools, tool, escapedName) => { let t = $tools.data('listtools'); let $duplicate = escapedName && $tools.children('.listtools-' + escapedName); let url = tool.url; if (typeof url === 'function') { url = url(t); if (!url) { $duplicate?.remove(); return; } } let $link = $('<a>').attr('href', url).text(getMsg(tool.name)); if (t.host) { $link.prop('host', t.host); } if (tool.callback) { $link.on('click', tool.callback); } let $wrapper = $('<span>').addClass('listtools-' + tool.name) .append($link); let $next = tool.next && $tools.children('.listtools-' + tool.next); if ($next?.length) { $duplicate?.remove(); $next.before($wrapper); } else if ($duplicate?.length) { $duplicate.replaceWith($wrapper); } else { $tools.append($wrapper); } }; let extend = tool => { if (tool.label && !Object.hasOwn(messages, tool.label)) { messages[tool.name] = tool.label; } if (tool.next) { tool.next = $.escapeSelector(tool.next); } let existingTool = tools.find(t => t.name === tool.name); if (existingTool) { Object.assign(existingTool, tool); } else { tools.push(tool); } let escapedName = existingTool && $.escapeSelector(tool.name); let $allTools = $('.listtools'); $allTools.each(function (i) { addTool($allTools.eq(i), tool, escapedName); }); }; let getWatched = async titles => { if (!Array.isArray(titles)) { titles = [...titles].slice(0, 500); } if (!titles.length) return; (await new mw.Api().post({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages.forEach(page => { if (!page.watched) return; $(`.listtools-watch > a[href="${mw.util.getUrl(page.title, { action: 'watch' })}"]`) .attr('href', mw.util.getUrl(page.title, { action: 'unwatch' })) .text(getMsg('unwatch')); }); getWatched(titles.slice(50)); }; mw.hook('listtools.ready').fire(extend); let catTreeCallback = (records, observer) => { let $links = $(records[0].target).find('.CategoryTreeItem > bdi > a'); if ($links.length) { observer.takeRecords(); observer.disconnect(); processLinks($links, catTreeModule); } }; let catTreeModule = { selector: '.CategoryTreeItem > bdi > a', types: [14, 'CategoryTree'], position: 'end', post: $tools => { $tools.parent().next('.CategoryTreeChildren').each(function () { new MutationObserver(catTreeCallback) .observe(this, { childList: true }); }); } }; let modules = [ { selector: '#mw-pages li > a, #mw-pages li > span > a', types: [14] }, catTreeModule, { selector: '#mw-imagepage-section-linkstoimage a, #mw-imagepage-section-globalusage a', types: [6] }, { selector: '#mw-globalusage-result a', types: ['GlobalUsage'] }, { selector: '.mw-search-result-heading > a, .searchalttitle > a.mw-redirect, .iw-result__title > a, .mw-search-exists a', types: ['Search'] }, { selector: '.mw-search-createlink a', types: ['Search'], titlesOnly: true }, { selector: '#watchlist-edit-form .cdx-table td > label > a', types: ['EditWatchlist'] }, { selector: '.plainlinks > li > a', types: ['AbuseLog'], titlesOnly: true }, { selector: '#mw-allmessagestable td:first-child > a:first-child:not(.new)', types: ['Allmessages'], position: 'end' }, { selector: '.mw-spcontent li a', types: ['DisambiguationPageLinks', 'Listredirects'], titlesOnly: true }, { selector: 'li > a:first-child', types: ['FileDuplicateSearch'] }, { selector: '.TablePager_col_title > a:first-child, .TablePager_col_template > a', types: ['LintErrors'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: 'form > ul > li > a', types: ['Nuke'], position: 'end', titlesOnly: true }, { selector: '.page-assessments a', types: ['PageAssessments'], titlesOnly: true }, { selector: '.TablePager_col_pr_page > a', types: ['Protectedpages'], position: 'end' }, { selector: '#mw-content-text > ul a', types: ['Protectedtitles'], position: 'end' }, { selector: '.mw-fr-pending-changes-page-title', types: ['PendingChanges'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: '#mw-content-text > ul a:first-child', types: ['StablePages'], position: 'end' }, { selector: '.TablePager_col__page a', types: ['TopicSubscriptions'] }, { selector: '.undeleteResult > a', types: ['Undelete'], position: 'end', useText: true }, { selector: '.TablePager_col_img_name > a:first-child', // types: ['Listfiles'], position: 'end' }, { selector: '.mw-newpages-pagename', post: $tools => { let $nodes = $tools.parent().contents(); $nodes.slice( $nodes.index($tools) + 1, $nodes.index($nodes.filter('.mw-newpages-length')) ).replaceWith(' '); } }, { selector: '#mw-whatlinkshere-list li > bdi > a' }, { selector: '.mw-changeslist-log-entry > a:not(.mw-changeslist-log-gblblock a, .mw-changeslist-log-globalauth a)', titlesOnly: true }, { selector: '.mw-logevent-loglines > li:not(.mw-logline-gblblock, .mw-logline-globalauth) > a', types: ['Log'], titlesOnly: true }, { selector: '#mw-diff-otitle1 > strong > a, #mw-diff-ntitle1 > strong > a', types: ['ComparePages'], position: 'end' }, { selector: '#movepage-oldlink, #movepage-newlink', types: ['Movepage'] }, { selector: '.mw-undelete-revision a:not(.mw-userlink, .mw-usertoollinks > a)', types: ['Undelete'], useText: true }, { selector: '.galleryfilename, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a' }, { selector: '.mw-spcontent li a', position: 'end', titlesOnly: true } ]; if (isEdit) { let post = $tools => { if (!$tools[0].closest('.templatesUsed')) return; $tools.parent().contents().last().each(function () { this.textContent = this.textContent.slice(1); }).end().slice(-3, -1).remove(); }; let callback = mw.util.debounce(() => { processLinks( $('.mw-editfooter-list a, #wikiPreview > .previewnote a'), { titlesOnly: true, post } ); }, 500); mw.hook('wikipage.editform').add($form => { callback(); $form.find('.templatesUsed').each(function () { if (processed.has(this)) return; processed.add(this); new MutationObserver(callback) .observe(this, { childList: true, subtree: true }); }); }); } else if (typeof pageType === 'number') { $(() => { processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {}); }); } mw.hook('wikipage.content').add($content => { let titles = new Set(); let $links = $content.find('a'); modules.forEach(module => { if (module.types && !module.types.includes(pageType)) return; processLinks($links.filter(module.selector), module, titles); }); getWatched(titles); }); }()); mw.hook('listtools.ready').add(extend => { // extend({ // name: 'talk', // url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(), // next: 'hist' // }); extend({ name: 'subject', url: t => t.isTalkPage() && t.getSubjectPage().getUrl(), next: 'hist' }); extend({ name: 'last', url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }), next: 'links' }); // extend({ // name: 'purge', // url: t => t.getUrl({ action: 'purge' }), // next: 'watch', // callback: function (e) { // e.preventDefault(); // let $link = $(this); // let $wrapper = $link.parent(); // $link.detach(); // $wrapper.text('purging'); // let pn = $wrapper.closest('.listtools').data('listtools').toText(); // new mw.Api().post({ // action: 'purge', // forcelinkupdate: 1, // titles: pn, // formatversion: 2 // }).then(response => { // if (response.purge[0].purged) { // mw.notify(`Purged "${pn}"'`); // } // }).always(() => { // $wrapper.html($link); // }); // } // }); extend({ name: 'copy', url: '#', callback: function (e) { e.preventDefault(); let text = $(this).closest('.listtools').data('listtools').toText(); let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch (err) {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } } }); }); (mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') && mw.config.get('wgCanonicalSpecialPageName') !== 'GlobalContributions' && (function consecudiff() { mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}'); let isHist = mw.config.get('wgAction') === 'history'; class Consecudiff { constructor(lis, isContribs) { this.isContribs = isContribs; this.isEnhanced = !isHist && !isContribs && lis[0].classList.contains('mw-enhanced-rc'); this.threshold = isContribs ? window.consecudiffContribsThreshold || 120 : isHist ? window.consecudiffHistThreshold || 720 : window.consecudiffThreshold || 720; this.strictMode = !isContribs && !!window.consecudiffDetectInterruptions; this.diffSelector = isHist ? 'a.mw-history-histlinks-previous' : '.mw-changeslist-diff'; this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' || (isHist || isContribs) && 'a.mw-changeslist-date'; this.hybridSelector = this.diffSelector; if (this.permaSelector) { this.hybridSelector += ', ' + this.permaSelector; } this.topClass = isContribs ? 'mw-contributions-current' : 'mw-changeslist-last'; let dependencies = ['mediawiki.util']; if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') { dependencies.push('mediawiki.language.months'); } mw.loader.using(dependencies, () => { let chunks; if (isHist) { chunks = this.chunkByUser(lis); } else { chunks = []; this.groupByTitle(lis).forEach(group => { chunks.push(...this.chunkByUser(group)); }); } let subchunks = []; chunks.forEach(chunk => { subchunks.push(...this.divideByDate(chunk)); }); let linkPairs = []; subchunks.forEach(subchunk => { linkPairs.push(...this.makeLinks(subchunk)); }); linkPairs.forEach(([$span, parent]) => { $span.appendTo(parent); }); }); } groupByTitle(lis) { let selector = this.isContribs ? '.mw-contributions-title' : '.mw-changeslist-title'; let lisByTitle = {}; lis.forEach(li => { let link = (this.isEnhanced ? li.closest('table') : li) .querySelector(selector); if (!link) return; let title = link.textContent; if (!lisByTitle.hasOwnProperty(title)) { lisByTitle[title] = []; } lisByTitle[title].push(li); }); return Object.values(lisByTitle).filter(group => group.length > 1); } chunkByUser(lis) { if (this.isSingleContribs) { return [lis]; } let chunks = [], lastSplitAt = 0, prevUser; this.isSingleContribs = lis.some((li, i) => { let link = li.querySelector('.mw-userlink'); if (!link && this.isContribs) { return true; } let user = link && link.textContent; if (!link || i && user !== prevUser) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevUser = user; }); if (this.isSingleContribs) { return [lis]; } chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } divideByDate(lis) { let chunks = [], lastSplitAt = 0, prevDate; lis.forEach((li, i) => { let date; if (isHist || this.isContribs) { date = this.parseDate( li.querySelector('.mw-changeslist-date').textContent ); } else { date = Date.parse( li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z') ); } if (date) { date = date / 60000; } if (i && prevDate - date > this.threshold) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevDate = date; if (!this.strictMode || lastSplitAt === i) return; let prevDiff = lis[i - 1].querySelector(this.diffSelector); if (prevDiff) { let prevNext = mw.util.getParamValue('oldid', prevDiff.search); if (prevNext !== li.dataset.mwRevid) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } } }); chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } makeLinks(lis) { let count = lis.length; let firstPerma; let start = lis.findIndex(li => ( firstPerma = li.querySelector(this.hybridSelector) )); if (start === -1 || count - start < 2) return []; let end, lastDiff; for (let i = count - 1; i > start; i--) { if (!isHist && !this.isContribs) { lastDiff = lis[i].querySelector(this.diffSelector); if (lastDiff || lis[i].classList.contains('mw-changeslist-src-mw-new') ) { end = i + 1; break; } } if (this.permaSelector && lis[i].querySelector(this.permaSelector)) { end = i + 1; break; } } if (!end) return []; count = end - start; let params = { diff: lis[start].dataset.mwRevid }; if (lastDiff) { params.oldid = mw.util.getParamValue('oldid', lastDiff.search); } else { params.oldid = lis[end - 1].dataset.mwRevid; if (isHist && lis[end - 1].querySelector(this.diffSelector) || this.isContribs && !lis[end - 1].querySelector('.newpage') ) { params.direction = 'prev'; } } let title = !isHist && mw.util.getParamValue('title', firstPerma.search); let url = mw.util.getUrl(title, params); let classes = 'consecudiff'; if (!isHist && lis[start].classList.contains(this.topClass)) { classes += ' consecudiff-top'; } return lis.slice(start, end).map((li, i) => [ $('<span>').addClass(classes).append( $('<a>') .attr('href', url) .text(this.convertNumber(count - i + '/' + count)) ), this.isEnhanced ? li.tagName === 'TR' ? li.lastElementChild : li.querySelector('.mw-changeslist-line-inner') : li ]); } parseDate(s) { let date = Date.parse(s); if (date) { return date; } if (s.includes(',')) date = Date.parse(s.replace(',', '')); if (date) { return date; } if (mw.loader.getState('mediawiki.language.months') !== 'ready') return; s = s.replace(/\D/g, c => { let n = mw.language.convertNumber(c, true); return Number.isNaN(n) ? c : n; }); let h, m; s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => { h = $1; m = $2; return ' '; }); if (!h) return; let y, dateFirst; s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => { y = $2; dateFirst = /\d/.test($1); return $1 + ' '; }); if (!y) return; let mo, d; if (dateFirst) { [d, s] = this.getDate(s); if (!d) return; [mo, s] = this.getMonth(s); if (mo === -1) return; } else { [mo, s] = this.getMonth(s); if (mo === -1) return; [d, s] = this.getDate(s); if (!d) return; } return new Date(y, mo, d, h, m).getTime(); } getMonth(s) { if (!this.months) { this.months = mw.language.months.abbrev .concat(mw.language.months.names, mw.language.months.genitive) .reverse(); } let mo = this.months.findIndex(mn => { let temp = s.replace(mn, ' '); if (temp !== s) { s = temp; return true; } }); if (mo === -1) { let [numeric, temp] = this.getDate(s); numeric = parseInt(numeric); if (numeric > 0 && numeric < 13) { mo = numeric - 1; s = temp; } } else { mo = 11 - mo % 12; } return [mo, s]; } getDate(s) { let d; s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => { d = $2; return $1 + ' '; }); return [d, s]; } convertNumber(num) { try { return mw.language.convertNumber(num); } catch (e) { return num; } } } mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-body').each(function () { let lis = this.querySelectorAll('.mw-contributions-list > li'); if (lis.length > 1) { new Consecudiff([...lis], !isHist); } }); if (isHist) return; let $lists = $content.filter('.mw-changeslist'); if (!$lists.length) { $lists = $content.find('.mw-changeslist'); } $lists.each(function () { let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]'); if (lis.length > 1) { new Consecudiff([...lis]); } }); }); }()); if (mw.config.get('wgNamespaceNumber') === 14 && ( mw.config.get('wgAction') === 'view' || !mw.config.get('wgArticleId') )) { mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.UserInputWidget', 'mediawiki.widgets.datetime', 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement', 'mediawiki.interface.helpers.styles', 'user.options' ]); } $(function moveHistory() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Move history', 't-movehistory').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.moveHistoryDialog) { window.moveHistoryDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter', 'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions', 'mediawiki.interface.helpers.styles' ]); }); }); }); $(function sectionSearch() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Section search', 't-sectionsearch').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.sectionSearchDialog) { window.sectionSearchDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows', 'mediawiki.widgets', 'mediawiki.widgets.NamespacesMultiselectWidget' ]); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth' && mw.loader.using('jquery.tablesorter', function sortCentralAuthByEditCount() { mw.hook('wikipage.content').add($content => { let $table = $content.find('.mw-centralauth-wikislist').has('td'); if (!$table.length) return; $table.tablesorter().data('tablesorter').sort([{ 4: 'desc' }, { 1: 'asc' }]); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && [10, 828].includes(mw.config.get('wgNamespaceNumber')) && !mw.config.get('wgTitle').endsWith('/doc') && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoTestcases.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/TemplatePreviewGuard.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // $(function templatePreviewGuard() { // let button = document.querySelector('input[name="wpTemplateSandboxPreview"]'); // if (!button) return; // let proceed; // button.addEventListener('click', e => { // if (proceed) { // proceed = false; // return; // } // e.preventDefault(); // e.stopPropagation(); // let formData = new FormData(button.form); // let page = formData.get('wpTemplateSandboxPage'); // let temp = formData.get('wpTemplateSandboxTemplate'); // if (!page || !temp) return; // mw.loader.using('mediawiki.api').then(() => ( // new mw.Api().get({ // action: 'query', // titles: page, // prop: 'templates', // tltemplates: temp, // formatversion: 2 // }) // )).always(response => { // if (((((response || {}).query || {}).pages || [])[0] || {}).templates || // confirm(`"${page}" doesn't appear to transclude "${temp}". Continue?`) // ) { // proceed = true; // button.click(); // } // }); // }, true); // if (!mw.config.get('wgArticleId')) return; // let widgetEl = document.querySelector('#wpTemplateSandboxPage.oo-ui-widget'); // if (!widgetEl) return; // let pn = mw.config.get('wgPageName').replace(/_/g, ' '); // mw.loader.using(['mediawiki.api', 'oojs-ui-core']).then(() => ( // new mw.Api().get({ // action: 'query', // titles: pn, // prop: 'transcludedin', // tiprop: 'title', // tilimit: 'max', // formatversion: 2 // }) // )).then(response => { // if (!response.batchcomplete) return; // let pages = response.query.pages[0].transcludedin // .filter(o => o.title !== pn); // if (!pages.length) return; // let widget = OO.ui.infuse(widgetEl); // if (pages.length === 1) { // widget.setValue(pages[0].title); // return; // } // widget.$element.replaceWith( // new OO.ui.ComboBoxInputWidget({ // id: 'wpTemplateSandboxPage', // maxlength: widget.$input.prop('maxLength'), // name: widget.$input.prop('name'), // options: pages // .sort((a, b) => a.ns - b.ns || -(a.title < b.title)) // .map(o => ({ data: o.title })), // placeholder: widget.$input.prop('placeholder'), // tabIndex: widget.getTabIndex(), // value: widget.getValue() // }).on('enter', e => { // e.preventDefault(); // button.click(); // }).$element // ); // }); // }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoSectionLink.js&action=raw&ctype=text/javascript', 's'); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (function copyRevId() { let handler = function (e) { e.preventDefault(); let text = this.closest('.diff td')?.querySelector('[data-mw-revid]')?.dataset.mwRevid || this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!text) return; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); document.execCommand('copy'); $input.remove(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id') ) ); }); }()); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (() => { let handler = async function (e) { e.preventDefault(); let td = this.closest('.diff td'); let rev = td ? td.querySelector('[data-mw-revid]')?.dataset.mwRevid : this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!rev) { mw.notify(`Couldn't get the revision.`, { tag: 'markasunseen', type: 'error' }); return; } let pn = td ? new URLSearchParams([...td.querySelectorAll('a')].pop()?.search).get('title') : mw.config.get('wgPageName'); if (!pn) return; await mw.loader.using('mediawiki.api'); let result = (await new mw.Api().postWithEditToken({ action: 'setnotificationtimestamp', [td ? 'newerthanrevid' : 'torevid']: rev, titles: pn, formatversion: 2 })).setnotificationtimestamp?.[0]; if (Object.hasOwn(result, 'notificationtimestamp')) { mw.notify(`Marked revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'success' }); } else if (result?.notwatched) { mw.notify('This page is not on your watchlist.', { tag: 'markasunseen', type: 'warn' }); } else { mw.notify(`Couldn't mark revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions after this one as unseen' }).on('click', handler).text('unseen'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions since this one as unseen' }).on('click', handler).text('unseen') ) ); }); })(); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && ((mw.config.get('wgNamespaceNumber') % 2 || mw.config.get('wgNamespaceNumber') === 4) || (mw.config.get('wgWikiID') === 'metawiki' && mw.config.get('wgPageContentModel') === 'wikitext')) && mw.loader.using(['mediawiki.util', 'mediawiki.Title'], function copyUnsig() { let handler = function (e) { e.preventDefault(); let parent = this.closest('li, td'); let ts = parent.textContent.match(/\d\d:\d\d, \d\d? [A-Z][a-z]+ \d{4}/)?.[0]; if (!ts) return; let user = parent.querySelector('.mw-userlink').textContent; if (mw.util.isIPv6Address(user)) { user = user.toUpperCase(); } let temp = mw.util.isIPAddress(user) ? 'unsigned IP' : 'unsigned'; let text = `{{subst:${temp}|${user}|${ts}}}`; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').filter(function () { if (mw.config.get('wgWikiID') === 'metawiki') { return true; } let link = this.querySelector('strong > a') || this.parentElement.querySelector('#differences-prevlink, #differences-nextlink'); if (!link) return; let t = mw.Title.newFromText(mw.util.getParamValue('title', link.search)); return t.isTalkPage() || t.namespace === 4; }).append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig') ) ); }); }); // mw.config.get('wgAction') === 'history' && // mw.loader.using('mediawiki.util', function () { // mw.hook('wikipage.content').add($content => { // $content.find('a.mw-changeslist-date').after(function () { // return [ // ' (', // $('<a>').attr('href', mw.util.getUrl(null, { // action: 'edit', // oldid: this.closest('li').dataset.mwRevid // })).text('e'), // ')' // ]; // }); // }); // }); // ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && // mw.hook('wikipage.content').add($content => { // $content.find('.mw-changeslist-history').parent().after(function () { // return $('<span>').append( // $('<a>').attr( // 'href', // this.firstElementChild.getAttribute('href').slice(0, -7) + 'edit' // ).text('e') // ); // }); // }); if (screen.width < 500) { mw.loader.addStyleTag('@font-face{font-family:CharisW;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/017b2b2ad86e09d3c22b8cf0dfc78247/CharisSILRegular.ttf) format(truetype)} @font-face{font-family:CharisW;font-weight:700;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/6f5069ac6a300dad45383c952e92c573/CharisSILBold.ttf) format(truetype)} body .IPA{font-family:CharisW,sans-serif} .mw-highlight-lines > pre{width:120em}'); location.hash && $(() => { let target = document.querySelector(':target'); if (target?.getBoundingClientRect().top < 0) { target.scrollIntoView(); } }); } ['edit', 'submit'].includes(mw.config.get('wgAction')) && (mw.config.exists('wgCodeEditorCurrentLanguage') || mw.config.exists('cmMode') && mw.config.get('cmMode') !== 'mediawiki') && (function saveNEdit() { let notif; $(document.body).on('click', '#wpSave', async function (e) { if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey || e.originalEvent?.defaultPrevented ) { return; } e.preventDefault(); await mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'jquery.textSelection', 'oojs-ui-core' ]); let button = OO.ui.infuse(this.parentElement).setDisabled(true); let $textarea = $('#wpTextbox1'); let text = $textarea.textSelection('getContents'); let $summary = $('#wpSummary'); let formData = new FormData(this.form); let promise = new mw.Api().postWithEditToken({ action: 'edit', title: mw.config.get('wgPageName'), text: text, section: formData.get('wpSection') || undefined, summary: $summary.textSelection('getContents'), [$('#wpMinoredit').prop('checked') ? 'minor' : 'notminor']: 1, baserevid: formData.get('editRevId'), basetimestamp: formData.get('wpEdittime'), starttimestamp: formData.get('wpStarttime'), watchlist: $('#wpWatchthis').prop('checked') ? 'watch' : 'unwatch', watchlistexpiry: formData.get('wpWatchlistExpiry') || undefined, undo: formData.get('wpUndidRevision') || undefined, undoafter: formData.get('wpUndoAfter') || undefined, contentformat: formData.get('format'), contentmodel: formData.get('model'), assertuser: mw.config.get('wgUserName'), formatversion: 2 }); notif?.close(); notif = null; try { let response = await promise; if (response?.edit?.result !== 'Success') throw ''; $('#editform > input[name="wpUndidRevision"], #editform > input[name="wpUndoAfter"]').remove(); $textarea.data('origtext', text).prop('defaultValue', text); $summary.val($summary.prop('defaultValue')); if (mw.loader.getState('mediawiki.editRecovery.edit') === 'ready') { let storage = mw.loader.moduleRegistry['mediawiki.editRecovery.edit'].packageExports['storage.js']; storage.deleteData(mw.config.get('wgPageName')); storage.closeDatabase(); } notif = await mw.notify(response.edit.nochange ? 'No change' : [ document.createTextNode('Saved'), $('<p>').append( new OO.ui.ButtonWidget({ href: mw.util.getUrl(), target: '_blank', label: 'View' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { diff: response.edit.newrevid || 'cur', diffonly: 1 }), target: '_blank', label: 'Diff' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { action: 'history' }), target: '_blank', label: 'History' }).$element )[0] ], { tag: 'savenedit' }); } catch (error) { notif = await mw.notify(error?.error?.info || error || 'Save failed', { autoHideSeconds: 'long', tag: 'savenedit', type: 'error' }); } finally { button.setDisabled(); } }); }()); mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgAction') === 'view' && mw.config.get('wgCategories')?.some(c => c.endsWith(' actors') || c.endsWith(' actresses')) && $(() => { let n = $.escapeSelector(mw.config.get('wgTitle').replace(/ \(.+\)$/, '')); let $links = $(`.hatnote a[title$="${n} filmography"], .hatnote a[title*="${n} on "], .hatnote a[title*="${n} performances"]`); if (!$links.length) return; let titles = {}; $links = $links.filter(function () { let text = this.textContent; return !(titles[text] = Object.hasOwn(titles, text)); }); mw.notify( $links.length === 1 ? $links.clone() : $('<ul>').append($links.clone().wrap('<li>').parent()), { autoHideSeconds: 'long' } ); }); ['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/RCMuter.js&action=raw&ctype=text/javascript', 's'); location.hostname.endsWith('.wikipedia.org') && mw.config.get('wgNamespaceNumber') % 2 === 0 && // mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.util')).then(function refRenamer() { if (!document.getElementById('p-tb')) return; let messages = Object.assign({ portlet: 'RefRenamer', loading: 'Loading RefRenamer...' }, window.refrenamerMessages); let clicked; mw.util.addPortletLink('p-tb', '#', messages.portlet, 't-refrenamer').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.refRenamer) { window.refRenamer(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript'); mw.notify(messages.loading, { autoHideSeconds: 'long', tag: 'refrenamer' }); }); }); if (['edit', 'submit'].includes(mw.config.get('wgAction'))) { mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/ExpandContractions.js&action=raw&ctype=text/javascript', 's'); mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/Unpipe.js&action=raw&ctype=text/javascript', 's'); } mw.config.get('wgAction') !== 'history' && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopyCodeBlock.js&action=raw&ctype=text/javascript', 's'); mw.config.exists('wgDiffNewId') && mw.config.get('wgDiscussionToolsFeaturesEnabled') && (function () { let data = {}, clickHandler, autoClear, run; window.dtc = data; let highlight = revId => { let ids = data[revId]; if (!ids || !ids.length) return; mw.loader.moduleRegistry['ext.discussionTools.init'].packageExports['highlighter.js'] .highlightNewComments(mw.dt.pageThreads, true, ids); if (clickHandler) { $(document.body).off('click', clickHandler); return; } $._data(document.body, 'events').click.some(o => { if (String(o.handler).includes('highlighter.clearHighlightTargetComment(')) { $(document.body).off('click', o.handler); clickHandler = o.handler; return true; } }); $._data(window, 'events').popstate.some(o => { if (String(o.handler).includes('highlighter.highlightTargetComment(')) { $(window).off('popstate', o.handler); return true; } }); }; let scroll = revId => { let ids = data[revId]; if (!ids || !ids.length) return; let yToSpan = Object.fromEntries( ids.map(id => document.getElementById(id)).filter(Boolean) .map(span => [span.getBoundingClientRect().y, span]) ); let ys = Object.keys(yToSpan); if (!ys.length) return; let lower = ys.filter(y => y > 10); if (!lower.length || Math.max(...lower) < document.documentElement.clientHeight ) { yToSpan[Math.min(...ys)].scrollIntoView(); } else { yToSpan[Math.min(...lower)].scrollIntoView(); } }; let scrollToNext = function (e) { e.preventDefault(); let revId = mw.config.get('wgDiffOldId'); if (!revId || !data[revId]) return; let i = data[revId].indexOf( this.closest('[data-mw-thread-id]').dataset.mwThreadId ); if (i === -1) return; let next = data[revId][i + 1] || data[revId][0]; document.getElementById(next).scrollIntoView(); }; mw.hook('wikipage.content').add(async $content => { let revId = mw.config.get('wgDiffOldId'); if (!revId) return; let param = new URLSearchParams(location.search).get('diffonly'); if (param && param !== '0') return; if (data[revId]) { highlight(revId); return; } await mw.loader.using(['ext.discussionTools.init', 'mediawiki.util']); let begin = Date.parse($('#mw-diff-otitle1 .mw-diff-timestamp').data('timestamp')); data[revId] = mw.dt.pageThreads.getCommentItems() .filter(c => c.timestamp > begin).map(c => c.id); if (!data[revId].length) return; await new Promise(setTimeout); highlight(revId); $content.find('.ext-discussiontools-init-replylink-buttons').filter(function () { return data[revId].includes(this.dataset.mwThreadId); }).children('span:last-of-type').before( ' | ', $('<a>').attr({ href: '#', role: 'button' }).text('next').on('click', scrollToNext) ); if (run || !document.getElementById('p-tb')) return; run = true; let portlet = mw.util.addPortletLink('p-tb', '#', 'Scroll to next', 't-scrolltonext'); portlet.firstElementChild.addEventListener('click', e => { e.preventDefault(); scroll(mw.config.get('wgDiffOldId')); }); mw.util.addPortletLink('p-tb', '#', 'Toggle highlight', 't-togglehighlight').firstElementChild.addEventListener('click', e => { e.preventDefault(); autoClear = !autoClear; if (autoClear) { $(document.body).on('click', clickHandler)[0].click(); } else { highlight(mw.config.get('wgDiffOldId')); } }); mw.loader.addStyleTag(`#t-scrolltonext{position:fixed;bottom:${portlet.clientHeight}px} #t-togglehighlight{position:fixed;bottom:0}`); }); }()); mw.config.get('wgNamespaceNumber') === 6 && mw.config.get('wgAction') === 'view' && mw.hook('wikipage.content').add($content => { $content.find('.filehistory .mw-usertoollinks-contribs').after(function () { return [ ' | ', $('<a>').attr('href', `${ mw.config.get('wgScript') }?title=Special:ListFiles/${ this.pathname.replace(/^.+\//, '') }&ilshowall=1`).text('uploads') ]; }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(function () { if (!$('input[name="wpSection"]').val()) return; mw.hook('wikipage.content').add(async $content => { let $refs = $content.find('.mw-ext-cite-warning-sectionpreview_no_text'); if (!$refs.length) return; let ids = {}; $refs.each(function () { ids[this.closest('[id]').id.replace(/-\d+$/, '')] = this; }); let response = await $.get(`/api/rest_v1/page/html/${encodeURIComponent(mw.config.get('wgPageName'))}`); $($.parseHTML(response)).find('.mw-reference-text').each(function () { ids[this.id.replace(/^mw-reference-text-|-\d+$/g, '')]?.replaceWith(this); }); }); }); mw.hook('moremenu.ready').add(config => { $('#mm-page-purge-cache > a').on('click', e => { e.preventDefault(); new mw.Api().post({ action: 'purge', forcelinkupdate: 1, titles: config.page.name, formatversion: 2 }).then(() => { location.href = mw.util.getUrl(); }); }); $('#mm-page-search-search-history-wikiblame > a').on('click', function (e) { e.preventDefault(); let q = prompt(); if (q === null) return; let href = this.href; if (q) { let removal = q[0] === '!'; if (removal) { q = q.slice(1); } href += '&needle=' + encodeURIComponent(q); if (removal) { href += '&binary_search_inverse=on'; } href += '&force_wikitags=on'; } open(href, '_blank'); }); $('#mm-page-expand-templates > a').on('click auxclick', function (e) { if (e.which > 2) return; e.preventDefault(); let revId = mw.config.get('wgRevisionId') || Number($('input[name=oldid]').val()); let url = revId ? '/w/rest.php/v1/revision/' + revId : '/w/rest.php/v1/page/' + config.page.encodedName; $.get(url).then(response => { $('<form>').attr({ method: 'post', action: this.href, target: '_blank' }).append( [ ['wpInput', response.source], ['wpContextTitle', config.page.name], ['wpRemoveComments', 1] ].map(([n, v]) => $('<input>').attr({ name: n, type: 'hidden' }).val(v)) ).appendTo(document.body).trigger('submit').remove(); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'ApiSandbox' && mw.hook('apisandbox.formatRequest').add((...args) => { args[4].complete = function () { setTimeout(() => { mw.hook('wikipage.content').fire($('.oo-ui-pageLayout-active')); }, 100); }; }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.storage')).then(async () => { let infuseAndCall = (query, method, ...args) => { let $widget = $(query); if ($widget.length) { return OO.ui.infuse($widget)[method](...args); } }; let section = $('input[name="wpSection"]').val(); if (section) { let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let save = () => { let newSource = $textarea.textSelection('getContents'); if (newSource === source) { mw.storage.session.remove('editfullpage'); } else { mw.storage.session.setObject('editfullpage', [ mw.config.get('wgPageName'), section, newSource.trimEnd(), infuseAndCall('#wpSummaryWidget', 'getValue') || '', Number(infuseAndCall('#wpMinoreditWidget', 'isSelected')) || 0, Number(infuseAndCall('#wpWatchthisWidget', 'isSelected')) || 0, infuseAndCall('#wpWatchlistExpiryWidget', 'getValue') || 'infinite' ]); } }; await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); setInterval(() => { mw.requestIdleCallback(save); }, 3000); window.addEventListener('beforeunload', save); return; } let data = mw.storage.session.getObject('editfullpage'); mw.storage.session.remove('editfullpage'); console.log(data); if (!data || data[0] !== mw.config.get('wgPageName')) return; let isNew = data[1] === 'new'; let isLead = data[1] === '0'; let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let newSource, start, msg, notifOpts = { autoHideSeconds: 'long' }; let orig = []; if (isNew) { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); newSource = source + (data[3] ? '\n== ' + data[3] + ' ==\n\n' : '\n') + data[2] + '\n'; start = source.length; } else { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core', 'mediawiki.api']); let { parse } = await new mw.Api().get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'sections', wrapoutputclass: '', disablelimitreport: 1, disableeditsection: 1, disabletoc: 1, formatversion: 2 }); let target = !isLead && parse.sections.find(s => s.index === data[1]); if (isLead || target) { let next = parse.sections.find(s => s.index - 1 === Number(data[1])); newSource = (isLead ? '' : [...source].slice(0, target.byteoffset)).join('') + data[2] + (next ? '\n\n' + [...source].slice(next.byteoffset).join('') : '\n'); start = isLead ? 0 : target.byteoffset; } else { newSource = source + '\n\n' + data[2] + '\n'; start = source.length; msg = `Section restored. Couldn't find the section. The source is appended at bottom.`; notifOpts.type = 'warn'; } orig[0] = infuseAndCall('#wpSummaryWidget', 'getValue'); infuseAndCall('#wpSummaryWidget', 'setValue', data[3]); } $textarea.textSelection('setContents', newSource); orig[1] = infuseAndCall('#wpMinoreditWidget', 'getSelected'); infuseAndCall('#wpMinoreditWidget', 'setSelected', data[4]); orig[2] = infuseAndCall('#wpWatchthisWidget', 'getSelected'); infuseAndCall('#wpWatchthisWidget', 'setSelected', data[5]); orig[3] = infuseAndCall('#wpWatchlistExpiryWidget', 'getValue'); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', data[6]); setTimeout(() => { $textarea.textSelection('setSelection', { start }); }); let notif = await mw.notify($([ document.createTextNode(msg || 'Section restored.'), $('<p>').append( new OO.ui.ButtonWidget({ flags: 'destructive', label: 'Discard' }).on('click', () => { $textarea.textSelection('setContents', source); if (orig[0]) { infuseAndCall('#wpSummaryWidget', 'setValue', orig[0]); } infuseAndCall('#wpMinoreditWidget', 'setSelected', orig[1]); infuseAndCall('#wpWatchthisWidget', 'setSelected', orig[2]); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', orig[3]); notif.close(); }).$element )[0] ]), notifOpts); }); mw.config.exists('wgPostEdit') && mw.loader.using('mediawiki.storage', () => { mw.storage.session.remove('editfullpage'); }); mw.config.get('wgAction') === 'history' && mw.hook('wikipage.content').add(async $content => { if (!$content.has('.mw-history-line-updated').length) return; let href = $content.find('a.mw-history-histlinks-current:not(.mw-history-line-updated a)').attr('href'); if (!href) { await mw.loader.using(['mediawiki.api', 'mediawiki.util']); let page = (await new mw.Api().get({ action: 'query', titles: mw.config.get('wgPageName'), prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev >= page.lastrevid) return; href = mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); } $content.find('.mw-history-compareselectedversions-button').first().after( ' ', $('<a>').attr({ class: 'unseendiff', href: href }).text('unseen') ); }); (async () => { let cspn = mw.config.get('wgCanonicalSpecialPageName'); let isBp = cspn === 'Blankpage'; if (!isBp && cspn !== 'Watchlist') return; await mw.loader.using('mediawiki.util'); let notify = async (text, options, pn) => { let msg = [document.createTextNode(text)]; if (pn) { msg.push( $('<p>').append( $('<a>').attr('href', mw.util.getUrl(pn)).text(pn), ' ', $('<span>').addClass('mw-changeslist-links').append( $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'edit' })) .text('edit') ), $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'history' })) .text('history') ) ) )[0] ); } if (isBp) { await $.ready; $('#mw-content-text').html(msg); } else { return mw.notify(msg, Object.assign(options || {}, { tag: 'unseendiff' })); } }; let getUrl = async pn => { await mw.loader.using('mediawiki.api'); let page = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; if (!page.notificationtimestamp) { notify(`Couldn't get the last seen time.`, { type: 'warn' }, pn); return; } let rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (rev === page.lastrevid) { notify('Already seen.', { type: 'warn' }, pn); return; } if (!rev) { rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, rvdir: 'newer', formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev) { notify(`Couldn't get the last seen revision.`, { type: 'warn' }, pn); return; } } if (rev > page.lastrevid) { notify(`Invalid rev for "${pn}" (rev: ${rev}, lastrevid: ${page.lastrevid})`, { autoHideSeconds: 'long', type: 'warn' }, pn); return; } return mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); }; if (isBp) { let pn = mw.config.get('wgTitle').match(/^[^/]+\/unseendiff\/(.+)$/)?.[1]; if (!pn) return; notify('Loading...', null, pn); let href = await getUrl(pn); if (!href) return; notify('Redirecting...', null, pn); location.href = href; return; } let handler = async function (e) { if (e.which > 2) return; e.preventDefault(); let pn = this.dataset.pn; if (!pn) { notify(`Couldn't get the page name.`, { type: 'error' }); return; } let notifPromise = notify('Loading...', { autoHideSeconds: 'long' }); let href = await getUrl(pn); if (!href) return; $(`.unseendiff-loader[data-pn="${$.escapeSelector(pn)}"]`).attr({ class: 'unseendiff', href: href, target: '_blank' }).off('click auxclick', handler); if (e.type === 'auxclick' || e.ctrlKey || e.metaKey || e.shiftKey) { open(href); } else { this.click(); } (await notifPromise).close(); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-changeslist-src-mw-edit.mw-changeslist-watchedunseen:not(.mw-changeslist-watchedseen) .mw-changeslist-line-inner' ).each(function () { let pn = this.dataset.targetPage || this.closest('[data-target-page]')?.dataset.targetPage || this.closest('table.mw-enhanced-rc')?.querySelector('[data-target-page]')?.dataset.targetPage; if (!pn) return; $('<span>').append( $('<a>').attr({ class: 'unseendiff-loader', href: mw.util.getUrl(`Special:BlankPage/unseendiff/${pn}`), 'data-pn': pn }).on('click auxclick', handler).text('unseen') ).appendTo( [...this.querySelectorAll('.mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); }); })(); ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.using('mediawiki.util', () => { let watched = new Set(); let query = async lis => { let titles = Object.keys(lis).slice(0, 50); if (!titles.length) return; await mw.loader.using('mediawiki.api'); let pages = (await new mw.Api().post({ action: 'query', titles: titles, prop: 'info', inprop: 'notificationtimestamp|watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages; for (let page of pages) { if (!Object.hasOwn(lis, page.title)) continue; if (page.watched) { watched.add(page); $(lis[page.title]).addClass('watched'); } if (!page.notificationtimestamp) continue; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev === page.lastrevid) continue; if (rev > page.lastrevid) { mw.notify($([ document.createTextNode('Invalid rev for "'), $('<a>').attr({ href: mw.util.getUrl(page.title, { action: 'history' }), target: '_blank' }).text(page.title)[0], document.createTextNode(`" (rev: ${rev}, lastrevid: ${page.lastrevid})`), ]), { autoHideSeconds: 'long', type: 'warn' }); continue; } $('<span>').append( $('<a>').attr({ class: 'unseendiff', href: mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }) }).text('unseen') ).appendTo( lis[page.title].map(li => ( [...li.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(li).before(' ') )) ); } titles.forEach(title => { delete lis[title]; }); query(lis); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-contributions-list > li:not(.mw-contributions-current)[data-mw-revid]' ).each(function () { let link = this.querySelector('a.mw-changeslist-date, a.mw-changeslist-history'); let pn = link ? new URLSearchParams(link.search).get('title') : ''; $('<span>').append( $('<a>').attr({ class: 'mw-changeslist-diff', href: mw.util.getUrl(pn, { diff: 'cur', oldid: this.dataset.mwRevid }) }).text('cur') ).appendTo( [...this.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); if (mw.config.get('wgWikiID') === 'wikidatawiki') return; let lis = {}; $content.find('.mw-contributions-title').each(function () { let title = this.textContent; if (!Object.hasOwn(lis, title)) { lis[title] = []; } lis[title].push(this.closest('li')); }); Object.keys(lis).forEach(title => { if (watched.has(title)) { $(lis[title]).addClass('watched'); delete lis[title]; } }); query(lis); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox9.js&action=raw&ctype=text/javascript'); mw.config.get('wgWikiID') === 'metawiki' && (async () => { let css = mw.loader.addStyleTag(`.wishtitle { font-size: 90%; font-style: italic; word-break: break-word; } .wishtitle > a { color: var(--color-warning, #886425); } .wishtitle > a:visited { color: var(--border-color-warning--hover, #735421); } .wishtitle-declined > a { text-decoration: line-through; } .wishtitle-declined > a:hover, .wishtitle-declined > a:focus, .mw-underline-always .wishtitle-declined > a { text-decoration: line-through underline; } #watchlist-edit-form .wishtitle { display: inline-block; } .mw-search-result-heading > .wishtitle, .catchangesviewer-table .wishtitle { display: block; } .catchangesviewer-table:has(.wishtitle) { white-space: wrap; }`); let lang = mw.config.get('wgUserLanguage'); let titles; let loadTitles = async () => { await mw.loader.using('mediawiki.storage'); titles = titles || mw.storage.getObject('wishtitles'); if (titles?.lang !== lang) { titles = { lang, w: [], fa: [] }; } }; let updateTitles = async (crwstatuses, crwcontinue) => { await mw.loader.using('mediawiki.api'); let params = { action: 'query', list: 'communityrequests-wishes', crwlang: lang, crwstatuses: crwstatuses, crwprop: 'title|updated', crwsort: 'updated', crwdir: 'ascending', crwlimit: 'max', crwcontinue: crwcontinue, formatversion: 2 }; if (!crwcontinue && !crwstatuses && titles._) { params.crwcontinue = `|${titles._}|0`; } let response = await new mw.Api().get(params); let wishes = response?.query?.['communityrequests-wishes']; if (wishes?.length) { let $span = $('<span>'); wishes.forEach(w => { let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1]; if (!id) return; titles.w[id - 1] = $span.html(w.title).text(); if (crwstatuses === 'declined') { (titles.wd = titles.wd || []).push(id - 1); } let faId = w.crfatitle?.match(/^Community Wishlist\/FA(\d+)/)?.[1]; if (!faId) return; titles.fa[faId - 1] = w.focusareatitle; }); if (!crwstatuses) { titles._ = wishes.at(-1).updated.replace(/\D/g, ''); } } let expiry = 86400; if (crwstatuses || crwcontinue) { let prev = mw.storage.getObject('_EXPIRY_wishtitles'); if (prev) { expiry = Math.round(Date.now() / 1000) + 86400 - prev; } } mw.storage.setObject('wishtitles', titles, expiry); crwcontinue = response?.continue?.crwcontinue; if (crwcontinue) { await updateTitles(crwstatuses, crwcontinue); } }; let getTitle = id => ( id[0] === 'W' ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1] ); let renderTitle = (title, id, tag = 'span') => { let classes = 'wishtitle'; if (id[0] === 'W' && titles.wd?.includes(id.slice(1) - 1)) { classes += ' wishtitle-declined'; } return $(`<${tag}>`).addClass(classes).append( $('<a>').attr({ href: `/wiki/Community_Wishlist/${id}`, title: `Community Wishlist/${id}` }).text(title) ); }; let callback = ([id, links]) => { let title = getTitle(id); if (!title) { return true; } $(links).after(' ', renderTitle(title, id)); }; let selector = '.mw-changeslist-title, ' + '.mw-changeslist-log-entry > a:not(.mw-userlink), ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' + '#watchlist-edit-form .cdx-table td > label > a, ' + '.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' + '.mw-contributions-title, ' + '#mw-whatlinkshere-list li > bdi > a, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-logevent-loglines > li > a, ' + '#mw-pages li > a, ' + '.catchangesviewer-table td:nth-child(3) > a'; mw.hook('wikipage.content').add(async $content => { let links = {}; $content.find('a').each(function () { if (!this.matches(selector)) return; let id = this.textContent.match( /^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/ )?.[1]; if (!id) return; (links[id] = links[id] || []).push(this); }); links = Object.entries(links); if (!links.length) return; await loadTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles('declined'); links.forEach(callback); }); let pn = mw.config.get('wgRelevantPageName'); let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1]; if (!id) return; await $.ready; let extTitle = document.querySelector('.ext-communityrequests-wish--title'); if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return; await loadTitles(); let title = getTitle(id); if (!title) { await updateTitles(); title = getTitle(id); if (!title) { await updateTitles('declined'); title = getTitle(id); if (!title) return; } } let $title = renderTitle(title, id, 'div'); if (mw.config.get('skin') === 'vector-2022') { $title.prependTo('.vector-page-toolbar'); } else { $title.insertAfter('#firstHeading'); } css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}'; if (extTitle) return; document.title = document.title.replace( pn.replaceAll('_', ' '), `${pn.replace(`Community_Wishlist/${id}`, title)} ($&)` ); })(); mw.config.get('wgWikiID') === 'metawiki' && mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => { if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return; let title = mw.config.get('wgTitle'); if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return; if (isWatched) { await new mw.Api().watch(title + '/Votes', expiry); mw.notify('Watching /Votes too.'); } else { await new mw.Api().unwatch(title + '/Votes'); mw.notify('Unwatched /Votes too.'); } }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && $(async () => { let $input = $('#wpTemplateSandboxTemplate'); if (!$input.length) return; mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}'); let makeTemplateField = () => new OO.ui.FieldLayout( new mw.widgets.TitleInputWidget({ inputId: 'wpTemplateSandboxTemplate', name: 'wpTemplateSandboxTemplate', showMissing: false, value: $input.val() }), { label: 'Template name:' } ); if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') { await mw.loader.using('mediawiki.widgets'); $input.parent().replaceWith(makeTemplateField().$element); return; } let require = await mw.loader.using([ 'ext.TemplateSandbox.TemplateSandboxTitleWidget', 'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options' ]); let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({ $overlay: true, id: 'wpTemplateSandboxPage', maxLength: 255, name: 'wpTemplateSandboxPage', placeholder: 'Page title', required: false, tabIndex: 10, templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val() }); widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}') .data('oouiInfused', widget); let fieldset = new OO.ui.FieldsetLayout({ classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'], id: 'templatesandbox-editform', items: [ makeTemplateField(), new OO.ui.ActionFieldLayout( widget, new OO.ui.ButtonInputWidget({ id: 'wpTemplateSandboxPreview', name: 'wpTemplateSandboxPreview', label: 'Show preview', tabIndex: 10, type: 'submit', useInputTag: true }), { align: 'top' } ) ], label: 'Preview page with this template' }); fieldset.$label.append('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await $.get( `https://en.wikipedia.org/w/rest.php/v1/page/${encodeURIComponent(xfd.page)}/html` ))); await queryTitles(xfd, xfd.titleExtractor($page)); }; let queryCat = async (xfd, gcmcontinue) => { let response = await new mw.Api().get({ action: 'query', prop: 'info|categories', inprop: 'watched', clprop: 'sortkey', clcategories: `Category:${xfd.cat}`, generator: 'categorymembers', gcmtitle: `Category:${xfd.cat}`, gcmlimit: 'max', gcmsort: 'timestamp', gcmdir: 'older', gcmcontinue: gcmcontinue, formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched && p.categories?.[0]?.sortkeyprefix !== ' ') { xfd.pages.push(p.title); } }); if (response?.continue?.gcmcontinue) { await queryCat(xfd, response.continue.gcmcontinue); } }; let show = async (xfd, lastId, isCache) => { if (xfd.portlet && isCache) return; let portletId = 'p-xfdnotifier-' + xfd.id; if (xfd.portlet) { $(xfd.portlet).find('ul').empty(); if (!xfd.pages.length) return; } else { await $.ready; xfd.portlet = mw.util.addPortlet(portletId, xfd.label, '#' + lastId); } let $label = $(`#${portletId}-label`).attr('title', xfd.full); if (xfd.page) { $label.wrapInner($('<a>').attr('href', mw.util.getUrl(xfd.page))); } xfd.pages.forEach(p => { let t = mw.Title.newFromText(p); let isTalk = t.isTalkPage(); let $other = $('<a>').attr({ href: t[isTalk ? 'getSubjectPage' : 'getTalkPage']().getUrl(), title: isTalk ? 'subject' : 'talk' }).text(isTalk ? 's' : 't'); let link = mw.util.addPortletLink(portletId, t.getUrl(), p).querySelector('a'); $('<span>').addClass('xfdnotifier-sublinks').append( $('<span>').append($other), $('<span>').append( $('<a>').attr({ href: t.getUrl({ action: 'history' }), title: 'history' }).text('h') ) ).insertAfter(link); }); }; mw.hook('wikipage.content').add(mw.util.throttle(async () => { let cache = mw.storage.getObject('xfdnotifier') || {}; let lastId = 'p-tb'; for (let xfd of xfds) { let portletId = 'p-xfdnotifier-' + xfd.id; let now = Math.floor(Date.now() / 1000); if (now - cache[xfd.id]?.[0] < 600) { xfd.pages = cache[xfd.id].slice(1); await show(xfd, lastId, true); lastId = portletId; continue; } xfd.pages = []; if (xfd.cat) { await queryCat(xfd); } else if (xfd.page) { await queryPage(xfd); } cache[xfd.id] = [now, ...xfd.pages]; mw.storage.setObject('xfdnotifier', cache, 604800); await show(xfd, lastId); lastId = portletId; } }, 1800000)); })(); 7q384ubnfnxo4h4uo3f703fm17yjxda 739830 739827 2026-04-30T06:27:29Z Nardog 40946 739830 javascript text/javascript (async function listTools() { let pageAction = mw.config.get('wgAction'); let isView = pageAction === 'view'; let isEdit = ['edit', 'submit'].includes(pageAction); if (!isView && !isEdit) return; let pageType = mw.config.get('wgCanonicalSpecialPageName') || mw.config.get('wgNamespaceNumber'); if (isView && !pageType && !mw.config.exists('wgRedirectedFrom') && !mw.config.get('wgIsRedirect') && !mw.config.get('wgPageName').includes('/') ) { return; } await mw.loader.using([ 'mediawiki.util', 'mediawiki.Title', 'mediawiki.api', 'mediawiki.interface.helpers.styles' ]); mw.loader.addStyleTag(`.listtools:not(#mw-content-subtitle .listtools) { font-size: 85%; } .listtools, .listtools a { font-weight: normal !important; font-style: normal; } .mw-datatable .listtools { display: block; } .listtools + .mw-whatlinkshere-tools, #watchlist-edit-form .listtools ~ .mw-changeslist-links, .mw-special-DisambiguationPageLinks .listtools + a { display: none; }`); let messages = Object.assign({ watched: 'Added "$1" to your watchlist', watchFail: `Couldn't watch "$1"`, unwatchFail: `Couldn't unwatch "$1"` }, window.listtoolsMessages); let getMsg = (key, ...args) => ( Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key ); let notif; let watchHandler = async function (e) { e.preventDefault(); let $link = $(this); let $wrapper = $link.parent(); $link.detach(); let params = new URLSearchParams(this.search); let action = params.get('action'); $wrapper.text(getMsg(action + 'ing')); let pn = params.get('title').replaceAll('_', ' '); let promise = new mw.Api()[action](pn); if (notif) { notif.close(); notif = null; } try { let result = await promise; if (!result || !result[action + 'ed']) throw ''; let newAction = action === 'watch' ? 'unwatch' : 'watch'; params.set('action', newAction); $link.add(`.listtools-watch > a[href="${this.pathname + this.search}"]`) .attr('href', this.pathname + '?' + params) .text(getMsg(newAction)); if (action !== 'watch') return; let require = await mw.loader.using([ 'mediawiki.notification', 'mediawiki.watchstar.widgets' ]); notif = await mw.notify( new (require('mediawiki.watchstar.widgets'))('watch', pn, null, $.noop, { message: getMsg('watched', pn) }).$element, { tag: 'listtools' } ); } catch { notif = await mw.notify(getMsg(action + 'Fail', pn), { tag: 'listtools', type: 'error' }); } finally { $wrapper.html($link); } }; let extGetMain = function () { return this.title; }; let re = new RegExp(`(?:\\?title=|${ mw.util.escapeRegExp(mw.format(mw.config.get('wgArticlePath'), '')) })([^#&?]+)`); let processed = new WeakSet(); let processLinks = ($links, module, titles) => { let isBatch = !!titles; titles = titles || new Set(); $links.each(function (i) { if (processed.has(this)) return; let $link = $links.eq(i); let pn; if (module.useText) { pn = $link.text(); } else { let match = $link.attr('href')?.match(re); if (!match) return; pn = decodeURIComponent(match[1]); } let t = mw.Title.newFromText(pn); if (!t) return; if (module.titlesOnly) { let text = $link.text(); if (text !== pn.replaceAll('_', ' ') && (text !== t.getMainText() || t.namespace === 2) ) { return; } } if ($link.is('.external, .extiw')) { Object.assign(t, { getMain: extGetMain, host: this.host, namespace: 0, title: pn }); } else { if (t.namespace < 0) return; if ($link.hasClass('new')) { t.missing = true; } titles.add(t.getSubjectPage().toText()); } let $tools = $('<span>').addClass('listtools mw-changeslist-links') .data('listtools', t); tools.forEach(tool => { addTool($tools, tool); }); if ($link.is(':is(del, bdi) > :only-child')) { if (module.position === 'end') { $link.parent().parent().append(' ', $tools); } else { $link.parent().after(' ', $tools); } } else if (module.position === 'end') { $link.parent().append(' ', $tools); } else { $link.after(' ', $tools); } if (module.post) { module.post($tools); } processed.add(this); }); if (!isBatch) { getWatched(titles); } }; let tools = [ { name: 'edit', url: t => t.getUrl({ action: 'edit' }) }, { name: 'hist', url: t => !t.missing && t.getUrl({ action: 'history' }) }, { name: 'links', url: t => mw.util.getUrl('Special:WhatLinksHere/' + t) }, { name: 'watch', url: t => !t.host && t.getSubjectPage().getUrl({ action: 'watch' }), callback: watchHandler } ]; let addTool = ($tools, tool, escapedName) => { let t = $tools.data('listtools'); let $duplicate = escapedName && $tools.children('.listtools-' + escapedName); let url = tool.url; if (typeof url === 'function') { url = url(t); if (!url) { $duplicate?.remove(); return; } } let $link = $('<a>').attr('href', url).text(getMsg(tool.name)); if (t.host) { $link.prop('host', t.host); } if (tool.callback) { $link.on('click', tool.callback); } let $wrapper = $('<span>').addClass('listtools-' + tool.name) .append($link); let $next = tool.next && $tools.children('.listtools-' + tool.next); if ($next?.length) { $duplicate?.remove(); $next.before($wrapper); } else if ($duplicate?.length) { $duplicate.replaceWith($wrapper); } else { $tools.append($wrapper); } }; let extend = tool => { if (tool.label && !Object.hasOwn(messages, tool.label)) { messages[tool.name] = tool.label; } if (tool.next) { tool.next = $.escapeSelector(tool.next); } let existingTool = tools.find(t => t.name === tool.name); if (existingTool) { Object.assign(existingTool, tool); } else { tools.push(tool); } let escapedName = existingTool && $.escapeSelector(tool.name); let $allTools = $('.listtools'); $allTools.each(function (i) { addTool($allTools.eq(i), tool, escapedName); }); }; let getWatched = async titles => { if (!Array.isArray(titles)) { titles = [...titles].slice(0, 500); } if (!titles.length) return; (await new mw.Api().post({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages.forEach(page => { if (!page.watched) return; $(`.listtools-watch > a[href="${mw.util.getUrl(page.title, { action: 'watch' })}"]`) .attr('href', mw.util.getUrl(page.title, { action: 'unwatch' })) .text(getMsg('unwatch')); }); getWatched(titles.slice(50)); }; mw.hook('listtools.ready').fire(extend); let catTreeCallback = (records, observer) => { let $links = $(records[0].target).find('.CategoryTreeItem > bdi > a'); if ($links.length) { observer.takeRecords(); observer.disconnect(); processLinks($links, catTreeModule); } }; let catTreeModule = { selector: '.CategoryTreeItem > bdi > a', types: [14, 'CategoryTree'], position: 'end', post: $tools => { $tools.parent().next('.CategoryTreeChildren').each(function () { new MutationObserver(catTreeCallback) .observe(this, { childList: true }); }); } }; let modules = [ { selector: '#mw-pages li > a, #mw-pages li > span > a', types: [14] }, catTreeModule, { selector: '#mw-imagepage-section-linkstoimage a, #mw-imagepage-section-globalusage a', types: [6] }, { selector: '#mw-globalusage-result a', types: ['GlobalUsage'] }, { selector: '.mw-search-result-heading > a, .searchalttitle > a.mw-redirect, .iw-result__title > a, .mw-search-exists a', types: ['Search'] }, { selector: '.mw-search-createlink a', types: ['Search'], titlesOnly: true }, { selector: '#watchlist-edit-form .cdx-table td > label > a', types: ['EditWatchlist'] }, { selector: '.plainlinks > li > a', types: ['AbuseLog'], titlesOnly: true }, { selector: '#mw-allmessagestable td:first-child > a:first-child:not(.new)', types: ['Allmessages'], position: 'end' }, { selector: '.mw-spcontent li a', types: ['DisambiguationPageLinks', 'Listredirects'], titlesOnly: true }, { selector: 'li > a:first-child', types: ['FileDuplicateSearch'] }, { selector: '.TablePager_col_title > a:first-child, .TablePager_col_template > a', types: ['LintErrors'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: 'form > ul > li > a', types: ['Nuke'], position: 'end', titlesOnly: true }, { selector: '.page-assessments a', types: ['PageAssessments'], titlesOnly: true }, { selector: '.TablePager_col_pr_page > a', types: ['Protectedpages'], position: 'end' }, { selector: '#mw-content-text > ul a', types: ['Protectedtitles'], position: 'end' }, { selector: '.mw-fr-pending-changes-page-title', types: ['PendingChanges'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: '#mw-content-text > ul a:first-child', types: ['StablePages'], position: 'end' }, { selector: '.TablePager_col__page a', types: ['TopicSubscriptions'] }, { selector: '.undeleteResult > a', types: ['Undelete'], position: 'end', useText: true }, { selector: '.TablePager_col_img_name > a:first-child', // types: ['Listfiles'], position: 'end' }, { selector: '.mw-newpages-pagename', post: $tools => { let $nodes = $tools.parent().contents(); $nodes.slice( $nodes.index($tools) + 1, $nodes.index($nodes.filter('.mw-newpages-length')) ).replaceWith(' '); } }, { selector: '#mw-whatlinkshere-list li > bdi > a' }, { selector: '.mw-changeslist-log-entry > a:not(.mw-changeslist-log-gblblock a, .mw-changeslist-log-globalauth a)', titlesOnly: true }, { selector: '.mw-logevent-loglines > li:not(.mw-logline-gblblock, .mw-logline-globalauth) > a', types: ['Log'], titlesOnly: true }, { selector: '#mw-diff-otitle1 > strong > a, #mw-diff-ntitle1 > strong > a', types: ['ComparePages'], position: 'end' }, { selector: '#movepage-oldlink, #movepage-newlink', types: ['Movepage'] }, { selector: '.mw-undelete-revision a:not(.mw-userlink, .mw-usertoollinks > a)', types: ['Undelete'], useText: true }, { selector: '.galleryfilename, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a' }, { selector: '.mw-spcontent li a', position: 'end', titlesOnly: true } ]; if (isEdit) { let post = $tools => { if (!$tools[0].closest('.templatesUsed')) return; $tools.parent().contents().last().each(function () { this.textContent = this.textContent.slice(1); }).end().slice(-3, -1).remove(); }; let callback = mw.util.debounce(() => { processLinks( $('.mw-editfooter-list a, #wikiPreview > .previewnote a'), { titlesOnly: true, post } ); }, 500); mw.hook('wikipage.editform').add($form => { callback(); $form.find('.templatesUsed').each(function () { if (processed.has(this)) return; processed.add(this); new MutationObserver(callback) .observe(this, { childList: true, subtree: true }); }); }); } else if (typeof pageType === 'number') { $(() => { processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {}); }); } mw.hook('wikipage.content').add($content => { let titles = new Set(); let $links = $content.find('a'); modules.forEach(module => { if (module.types && !module.types.includes(pageType)) return; processLinks($links.filter(module.selector), module, titles); }); getWatched(titles); }); }()); mw.hook('listtools.ready').add(extend => { // extend({ // name: 'talk', // url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(), // next: 'hist' // }); extend({ name: 'subject', url: t => t.isTalkPage() && t.getSubjectPage().getUrl(), next: 'hist' }); extend({ name: 'last', url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }), next: 'links' }); // extend({ // name: 'purge', // url: t => t.getUrl({ action: 'purge' }), // next: 'watch', // callback: function (e) { // e.preventDefault(); // let $link = $(this); // let $wrapper = $link.parent(); // $link.detach(); // $wrapper.text('purging'); // let pn = $wrapper.closest('.listtools').data('listtools').toText(); // new mw.Api().post({ // action: 'purge', // forcelinkupdate: 1, // titles: pn, // formatversion: 2 // }).then(response => { // if (response.purge[0].purged) { // mw.notify(`Purged "${pn}"'`); // } // }).always(() => { // $wrapper.html($link); // }); // } // }); extend({ name: 'copy', url: '#', callback: function (e) { e.preventDefault(); let text = $(this).closest('.listtools').data('listtools').toText(); let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch (err) {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } } }); }); (mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') && mw.config.get('wgCanonicalSpecialPageName') !== 'GlobalContributions' && (function consecudiff() { mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}'); let isHist = mw.config.get('wgAction') === 'history'; class Consecudiff { constructor(lis, isContribs) { this.isContribs = isContribs; this.isEnhanced = !isHist && !isContribs && lis[0].classList.contains('mw-enhanced-rc'); this.threshold = isContribs ? window.consecudiffContribsThreshold || 120 : isHist ? window.consecudiffHistThreshold || 720 : window.consecudiffThreshold || 720; this.strictMode = !isContribs && !!window.consecudiffDetectInterruptions; this.diffSelector = isHist ? 'a.mw-history-histlinks-previous' : '.mw-changeslist-diff'; this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' || (isHist || isContribs) && 'a.mw-changeslist-date'; this.hybridSelector = this.diffSelector; if (this.permaSelector) { this.hybridSelector += ', ' + this.permaSelector; } this.topClass = isContribs ? 'mw-contributions-current' : 'mw-changeslist-last'; let dependencies = ['mediawiki.util']; if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') { dependencies.push('mediawiki.language.months'); } mw.loader.using(dependencies, () => { let chunks; if (isHist) { chunks = this.chunkByUser(lis); } else { chunks = []; this.groupByTitle(lis).forEach(group => { chunks.push(...this.chunkByUser(group)); }); } let subchunks = []; chunks.forEach(chunk => { subchunks.push(...this.divideByDate(chunk)); }); let linkPairs = []; subchunks.forEach(subchunk => { linkPairs.push(...this.makeLinks(subchunk)); }); linkPairs.forEach(([$span, parent]) => { $span.appendTo(parent); }); }); } groupByTitle(lis) { let selector = this.isContribs ? '.mw-contributions-title' : '.mw-changeslist-title'; let lisByTitle = {}; lis.forEach(li => { let link = (this.isEnhanced ? li.closest('table') : li) .querySelector(selector); if (!link) return; let title = link.textContent; if (!lisByTitle.hasOwnProperty(title)) { lisByTitle[title] = []; } lisByTitle[title].push(li); }); return Object.values(lisByTitle).filter(group => group.length > 1); } chunkByUser(lis) { if (this.isSingleContribs) { return [lis]; } let chunks = [], lastSplitAt = 0, prevUser; this.isSingleContribs = lis.some((li, i) => { let link = li.querySelector('.mw-userlink'); if (!link && this.isContribs) { return true; } let user = link && link.textContent; if (!link || i && user !== prevUser) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevUser = user; }); if (this.isSingleContribs) { return [lis]; } chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } divideByDate(lis) { let chunks = [], lastSplitAt = 0, prevDate; lis.forEach((li, i) => { let date; if (isHist || this.isContribs) { date = this.parseDate( li.querySelector('.mw-changeslist-date').textContent ); } else { date = Date.parse( li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z') ); } if (date) { date = date / 60000; } if (i && prevDate - date > this.threshold) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevDate = date; if (!this.strictMode || lastSplitAt === i) return; let prevDiff = lis[i - 1].querySelector(this.diffSelector); if (prevDiff) { let prevNext = mw.util.getParamValue('oldid', prevDiff.search); if (prevNext !== li.dataset.mwRevid) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } } }); chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } makeLinks(lis) { let count = lis.length; let firstPerma; let start = lis.findIndex(li => ( firstPerma = li.querySelector(this.hybridSelector) )); if (start === -1 || count - start < 2) return []; let end, lastDiff; for (let i = count - 1; i > start; i--) { if (!isHist && !this.isContribs) { lastDiff = lis[i].querySelector(this.diffSelector); if (lastDiff || lis[i].classList.contains('mw-changeslist-src-mw-new') ) { end = i + 1; break; } } if (this.permaSelector && lis[i].querySelector(this.permaSelector)) { end = i + 1; break; } } if (!end) return []; count = end - start; let params = { diff: lis[start].dataset.mwRevid }; if (lastDiff) { params.oldid = mw.util.getParamValue('oldid', lastDiff.search); } else { params.oldid = lis[end - 1].dataset.mwRevid; if (isHist && lis[end - 1].querySelector(this.diffSelector) || this.isContribs && !lis[end - 1].querySelector('.newpage') ) { params.direction = 'prev'; } } let title = !isHist && mw.util.getParamValue('title', firstPerma.search); let url = mw.util.getUrl(title, params); let classes = 'consecudiff'; if (!isHist && lis[start].classList.contains(this.topClass)) { classes += ' consecudiff-top'; } return lis.slice(start, end).map((li, i) => [ $('<span>').addClass(classes).append( $('<a>') .attr('href', url) .text(this.convertNumber(count - i + '/' + count)) ), this.isEnhanced ? li.tagName === 'TR' ? li.lastElementChild : li.querySelector('.mw-changeslist-line-inner') : li ]); } parseDate(s) { let date = Date.parse(s); if (date) { return date; } if (s.includes(',')) date = Date.parse(s.replace(',', '')); if (date) { return date; } if (mw.loader.getState('mediawiki.language.months') !== 'ready') return; s = s.replace(/\D/g, c => { let n = mw.language.convertNumber(c, true); return Number.isNaN(n) ? c : n; }); let h, m; s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => { h = $1; m = $2; return ' '; }); if (!h) return; let y, dateFirst; s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => { y = $2; dateFirst = /\d/.test($1); return $1 + ' '; }); if (!y) return; let mo, d; if (dateFirst) { [d, s] = this.getDate(s); if (!d) return; [mo, s] = this.getMonth(s); if (mo === -1) return; } else { [mo, s] = this.getMonth(s); if (mo === -1) return; [d, s] = this.getDate(s); if (!d) return; } return new Date(y, mo, d, h, m).getTime(); } getMonth(s) { if (!this.months) { this.months = mw.language.months.abbrev .concat(mw.language.months.names, mw.language.months.genitive) .reverse(); } let mo = this.months.findIndex(mn => { let temp = s.replace(mn, ' '); if (temp !== s) { s = temp; return true; } }); if (mo === -1) { let [numeric, temp] = this.getDate(s); numeric = parseInt(numeric); if (numeric > 0 && numeric < 13) { mo = numeric - 1; s = temp; } } else { mo = 11 - mo % 12; } return [mo, s]; } getDate(s) { let d; s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => { d = $2; return $1 + ' '; }); return [d, s]; } convertNumber(num) { try { return mw.language.convertNumber(num); } catch (e) { return num; } } } mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-body').each(function () { let lis = this.querySelectorAll('.mw-contributions-list > li'); if (lis.length > 1) { new Consecudiff([...lis], !isHist); } }); if (isHist) return; let $lists = $content.filter('.mw-changeslist'); if (!$lists.length) { $lists = $content.find('.mw-changeslist'); } $lists.each(function () { let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]'); if (lis.length > 1) { new Consecudiff([...lis]); } }); }); }()); if (mw.config.get('wgNamespaceNumber') === 14 && ( mw.config.get('wgAction') === 'view' || !mw.config.get('wgArticleId') )) { mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.UserInputWidget', 'mediawiki.widgets.datetime', 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement', 'mediawiki.interface.helpers.styles', 'user.options' ]); } $(function moveHistory() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Move history', 't-movehistory').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.moveHistoryDialog) { window.moveHistoryDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter', 'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions', 'mediawiki.interface.helpers.styles' ]); }); }); }); $(function sectionSearch() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Section search', 't-sectionsearch').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.sectionSearchDialog) { window.sectionSearchDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows', 'mediawiki.widgets', 'mediawiki.widgets.NamespacesMultiselectWidget' ]); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth' && mw.loader.using('jquery.tablesorter', function sortCentralAuthByEditCount() { mw.hook('wikipage.content').add($content => { let $table = $content.find('.mw-centralauth-wikislist').has('td'); if (!$table.length) return; $table.tablesorter().data('tablesorter').sort([{ 4: 'desc' }, { 1: 'asc' }]); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && [10, 828].includes(mw.config.get('wgNamespaceNumber')) && !mw.config.get('wgTitle').endsWith('/doc') && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoTestcases.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/TemplatePreviewGuard.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // $(function templatePreviewGuard() { // let button = document.querySelector('input[name="wpTemplateSandboxPreview"]'); // if (!button) return; // let proceed; // button.addEventListener('click', e => { // if (proceed) { // proceed = false; // return; // } // e.preventDefault(); // e.stopPropagation(); // let formData = new FormData(button.form); // let page = formData.get('wpTemplateSandboxPage'); // let temp = formData.get('wpTemplateSandboxTemplate'); // if (!page || !temp) return; // mw.loader.using('mediawiki.api').then(() => ( // new mw.Api().get({ // action: 'query', // titles: page, // prop: 'templates', // tltemplates: temp, // formatversion: 2 // }) // )).always(response => { // if (((((response || {}).query || {}).pages || [])[0] || {}).templates || // confirm(`"${page}" doesn't appear to transclude "${temp}". Continue?`) // ) { // proceed = true; // button.click(); // } // }); // }, true); // if (!mw.config.get('wgArticleId')) return; // let widgetEl = document.querySelector('#wpTemplateSandboxPage.oo-ui-widget'); // if (!widgetEl) return; // let pn = mw.config.get('wgPageName').replace(/_/g, ' '); // mw.loader.using(['mediawiki.api', 'oojs-ui-core']).then(() => ( // new mw.Api().get({ // action: 'query', // titles: pn, // prop: 'transcludedin', // tiprop: 'title', // tilimit: 'max', // formatversion: 2 // }) // )).then(response => { // if (!response.batchcomplete) return; // let pages = response.query.pages[0].transcludedin // .filter(o => o.title !== pn); // if (!pages.length) return; // let widget = OO.ui.infuse(widgetEl); // if (pages.length === 1) { // widget.setValue(pages[0].title); // return; // } // widget.$element.replaceWith( // new OO.ui.ComboBoxInputWidget({ // id: 'wpTemplateSandboxPage', // maxlength: widget.$input.prop('maxLength'), // name: widget.$input.prop('name'), // options: pages // .sort((a, b) => a.ns - b.ns || -(a.title < b.title)) // .map(o => ({ data: o.title })), // placeholder: widget.$input.prop('placeholder'), // tabIndex: widget.getTabIndex(), // value: widget.getValue() // }).on('enter', e => { // e.preventDefault(); // button.click(); // }).$element // ); // }); // }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(async () => { let form = document.getElementById('editform'); if (!form) return; let formData = new FormData(form); let section = formData.get('wpSection'); if (section === 'new') return; let widget = document.getElementById('wpSummaryWidget'); if (!widget) return; let isOld = formData.get('altBaseRevId') > 0 || (formData.get('baseRevId') || formData.get('parentRevId')) !== formData.get('editRevId'); await mw.loader.using([ 'jquery.textSelection', 'mediawiki.util', 'mediawiki.api', 'oojs-ui-core', 'oojs-ui.styles.icons-editing-core' ]); let $textarea = $('#wpTextbox1'); let input = OO.ui.infuse(widget); let button = new OO.ui.ButtonWidget({ framed: false, icon: 'undo', classes: ['autosectionlink-button'], invisibleLabel: true, label: 'Restore previous section link' }).toggle().on('click', () => { let cache = button.getData(); input.setValue(input.getValue().replace( /^(\/\*.*?\*\/)?\s*/, cache[0] ? '/* ' + cache[0] + ' */ ' : '' )); updatePreview(cache[0]); cache.reverse(); }).on('toggle', () => { input.$input.css('width', `calc(100% - ${button.$element.width()}px)`); }); input.$input.after(button.$element); let update = mw.util.debounce($diff => { let lines = $textarea.textSelection('getContents').trimEnd().split('\n'); let firstLineNum, lastLineNum; if (isOld) { let i; $diff.find('td:last-child').each(function () { if (this.classList.contains('diff-lineno')) { i = this.textContent.replace(/\D+/g, '') - 1; } else if (this.classList.contains('diff-context')) { i++; } else if (this.classList.contains('diff-addedline')) { i++; if (!firstLineNum) { firstLineNum = i; } lastLineNum = i; } else if (this.classList.contains('diff-empty')) { if (!firstLineNum) { firstLineNum = i === 0 ? 1 : i; } lastLineNum = i; } }); } else { let origLines = $textarea.prop('defaultValue').trimEnd().split('\n'); firstLineNum = lines.findIndex((line, i) => line !== origLines[i]) + 1; if (!firstLineNum) { firstLineNum = lines.length < origLines.length ? lines.length : 1; } lastLineNum = lines.findLastIndex((line, i) => line !== origLines[i]) + 1; } let modLines = lines.slice(0, lastLineNum || 0); let re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/; let lowest = 7; modLines.slice(firstLineNum).forEach(line => { let match = line.match(re); if (match?.[1].length < lowest) { lowest = match[1].length; } }); let head; modLines.slice(0, firstLineNum).reverse().some(line => { let match = line.match(re); if (match?.[1].length < lowest) { head = match[2]; return true; } }); if (head) { head = head .replace(/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>|<!--.*?-->|\[(?:https?:)?\/\/[^\s\[\]]+\s([^\]]+)\]/gi, '$1$2$3') .replace(/''(.+?)''/g, '$1') .trim(); } else if (modLines.length && lowest === 7 && section < 1 && ( section === '0' || lines.slice(modLines.length).some(line => re.test(line)) )) { head = ''; } let v = input.getValue(); let match = v.match(/^\/\*\s*(.+?)\s*\*\/\s*/); let prev = match?.[1]; if (prev === head) return; input.setValue(( head || head === '' ? '/* ' + head + ' */ ' : '' ) + (match ? v.slice(match[0].length) : v)); button.setData([prev, head]).toggle(true); updatePreview(head); }, 500); let updatePreview = head => { let $comment = $('.mw-summary-preview > .comment'); if (!$comment.length) return; let url; if (head) { url = mw.util.getUrl() + '#' + head.replace(/[\s_]+/g, '_'); } else if (head === '') { head = mw.messages.get('autocomment-top', '(top)'); url = mw.util.getUrl(); } let paren = [...mw.messages.get('parentheses', '($1)')][0]; let $nodes = $comment.contents(); let $ac = $nodes.eq(1); if ($nodes[0]?.textContent === paren && $ac.is('.autocomment:first-child')) { if (head) { $ac.children('a').attr('href', url).children('bdi').text(head); } else { if ($nodes[2]?.nodeType === 3) { $nodes[2].textContent = $nodes[2].textContent.trimStart(); } $ac.remove(); } } else if (head) { let rtl = document.body.classList.contains('sitedir-rtl'); $comment.prepend( paren, $('<span>').addClass('autocomment').append( $('<a>').attr({ href: url, title: mw.config.get('wgPageName').replaceAll('_', ' ') }).text(rtl ? '←' : '→').append( $('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(head) ), mw.messages.get('colon-separator', ': ') ) ); if ($nodes[0]?.nodeType === 3) { let text = $nodes[0].textContent; if (text.startsWith(paren)) { text = text.slice(paren.length); } $nodes[0].textContent = ' ' + text; } } }; if (isOld) { mw.hook('wikipage.diff').add(update); } else { $textarea.on('input', update); mw.hook('ext.CodeMirror.input').add(update); update(); } new mw.Api().loadMessagesIfMissing(['autocomment-top', 'colon-separator', 'parentheses']); mw.loader.addStyleTag('.autosectionlink-button{position:absolute;top:0;right:0;margin:0}'); }); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (function copyRevId() { let handler = function (e) { e.preventDefault(); let text = this.closest('.diff td')?.querySelector('[data-mw-revid]')?.dataset.mwRevid || this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!text) return; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); document.execCommand('copy'); $input.remove(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id') ) ); }); }()); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (() => { let handler = async function (e) { e.preventDefault(); let td = this.closest('.diff td'); let rev = td ? td.querySelector('[data-mw-revid]')?.dataset.mwRevid : this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!rev) { mw.notify(`Couldn't get the revision.`, { tag: 'markasunseen', type: 'error' }); return; } let pn = td ? new URLSearchParams([...td.querySelectorAll('a')].pop()?.search).get('title') : mw.config.get('wgPageName'); if (!pn) return; await mw.loader.using('mediawiki.api'); let result = (await new mw.Api().postWithEditToken({ action: 'setnotificationtimestamp', [td ? 'newerthanrevid' : 'torevid']: rev, titles: pn, formatversion: 2 })).setnotificationtimestamp?.[0]; if (Object.hasOwn(result, 'notificationtimestamp')) { mw.notify(`Marked revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'success' }); } else if (result?.notwatched) { mw.notify('This page is not on your watchlist.', { tag: 'markasunseen', type: 'warn' }); } else { mw.notify(`Couldn't mark revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions after this one as unseen' }).on('click', handler).text('unseen'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions since this one as unseen' }).on('click', handler).text('unseen') ) ); }); })(); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && ((mw.config.get('wgNamespaceNumber') % 2 || mw.config.get('wgNamespaceNumber') === 4) || (mw.config.get('wgWikiID') === 'metawiki' && mw.config.get('wgPageContentModel') === 'wikitext')) && mw.loader.using(['mediawiki.util', 'mediawiki.Title'], function copyUnsig() { let handler = function (e) { e.preventDefault(); let parent = this.closest('li, td'); let ts = parent.textContent.match(/\d\d:\d\d, \d\d? [A-Z][a-z]+ \d{4}/)?.[0]; if (!ts) return; let user = parent.querySelector('.mw-userlink').textContent; if (mw.util.isIPv6Address(user)) { user = user.toUpperCase(); } let temp = mw.util.isIPAddress(user) ? 'unsigned IP' : 'unsigned'; let text = `{{subst:${temp}|${user}|${ts}}}`; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').filter(function () { if (mw.config.get('wgWikiID') === 'metawiki') { return true; } let link = this.querySelector('strong > a') || this.parentElement.querySelector('#differences-prevlink, #differences-nextlink'); if (!link) return; let t = mw.Title.newFromText(mw.util.getParamValue('title', link.search)); return t.isTalkPage() || t.namespace === 4; }).append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig') ) ); }); }); // mw.config.get('wgAction') === 'history' && // mw.loader.using('mediawiki.util', function () { // mw.hook('wikipage.content').add($content => { // $content.find('a.mw-changeslist-date').after(function () { // return [ // ' (', // $('<a>').attr('href', mw.util.getUrl(null, { // action: 'edit', // oldid: this.closest('li').dataset.mwRevid // })).text('e'), // ')' // ]; // }); // }); // }); // ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && // mw.hook('wikipage.content').add($content => { // $content.find('.mw-changeslist-history').parent().after(function () { // return $('<span>').append( // $('<a>').attr( // 'href', // this.firstElementChild.getAttribute('href').slice(0, -7) + 'edit' // ).text('e') // ); // }); // }); if (screen.width < 500) { mw.loader.addStyleTag('@font-face{font-family:CharisW;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/017b2b2ad86e09d3c22b8cf0dfc78247/CharisSILRegular.ttf) format(truetype)} @font-face{font-family:CharisW;font-weight:700;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/6f5069ac6a300dad45383c952e92c573/CharisSILBold.ttf) format(truetype)} body .IPA{font-family:CharisW,sans-serif} .mw-highlight-lines > pre{width:120em}'); location.hash && $(() => { let target = document.querySelector(':target'); if (target?.getBoundingClientRect().top < 0) { target.scrollIntoView(); } }); } ['edit', 'submit'].includes(mw.config.get('wgAction')) && (mw.config.exists('wgCodeEditorCurrentLanguage') || mw.config.exists('cmMode') && mw.config.get('cmMode') !== 'mediawiki') && (function saveNEdit() { let notif; $(document.body).on('click', '#wpSave', async function (e) { if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey || e.originalEvent?.defaultPrevented ) { return; } e.preventDefault(); await mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'jquery.textSelection', 'oojs-ui-core' ]); let button = OO.ui.infuse(this.parentElement).setDisabled(true); let $textarea = $('#wpTextbox1'); let text = $textarea.textSelection('getContents'); let $summary = $('#wpSummary'); let formData = new FormData(this.form); let promise = new mw.Api().postWithEditToken({ action: 'edit', title: mw.config.get('wgPageName'), text: text, section: formData.get('wpSection') || undefined, summary: $summary.textSelection('getContents'), [$('#wpMinoredit').prop('checked') ? 'minor' : 'notminor']: 1, baserevid: formData.get('editRevId'), basetimestamp: formData.get('wpEdittime'), starttimestamp: formData.get('wpStarttime'), watchlist: $('#wpWatchthis').prop('checked') ? 'watch' : 'unwatch', watchlistexpiry: formData.get('wpWatchlistExpiry') || undefined, undo: formData.get('wpUndidRevision') || undefined, undoafter: formData.get('wpUndoAfter') || undefined, contentformat: formData.get('format'), contentmodel: formData.get('model'), assertuser: mw.config.get('wgUserName'), formatversion: 2 }); notif?.close(); notif = null; try { let response = await promise; if (response?.edit?.result !== 'Success') throw ''; $('#editform > input[name="wpUndidRevision"], #editform > input[name="wpUndoAfter"]').remove(); $textarea.data('origtext', text).prop('defaultValue', text); $summary.val($summary.prop('defaultValue')); if (mw.loader.getState('mediawiki.editRecovery.edit') === 'ready') { let storage = mw.loader.moduleRegistry['mediawiki.editRecovery.edit'].packageExports['storage.js']; storage.deleteData(mw.config.get('wgPageName')); storage.closeDatabase(); } notif = await mw.notify(response.edit.nochange ? 'No change' : [ document.createTextNode('Saved'), $('<p>').append( new OO.ui.ButtonWidget({ href: mw.util.getUrl(), target: '_blank', label: 'View' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { diff: response.edit.newrevid || 'cur', diffonly: 1 }), target: '_blank', label: 'Diff' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { action: 'history' }), target: '_blank', label: 'History' }).$element )[0] ], { tag: 'savenedit' }); } catch (error) { notif = await mw.notify(error?.error?.info || error || 'Save failed', { autoHideSeconds: 'long', tag: 'savenedit', type: 'error' }); } finally { button.setDisabled(); } }); }()); mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgAction') === 'view' && mw.config.get('wgCategories')?.some(c => c.endsWith(' actors') || c.endsWith(' actresses')) && $(() => { let n = $.escapeSelector(mw.config.get('wgTitle').replace(/ \(.+\)$/, '')); let $links = $(`.hatnote a[title$="${n} filmography"], .hatnote a[title*="${n} on "], .hatnote a[title*="${n} performances"]`); if (!$links.length) return; let titles = {}; $links = $links.filter(function () { let text = this.textContent; return !(titles[text] = Object.hasOwn(titles, text)); }); mw.notify( $links.length === 1 ? $links.clone() : $('<ul>').append($links.clone().wrap('<li>').parent()), { autoHideSeconds: 'long' } ); }); ['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/RCMuter.js&action=raw&ctype=text/javascript', 's'); location.hostname.endsWith('.wikipedia.org') && mw.config.get('wgNamespaceNumber') % 2 === 0 && // mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.util')).then(function refRenamer() { if (!document.getElementById('p-tb')) return; let messages = Object.assign({ portlet: 'RefRenamer', loading: 'Loading RefRenamer...' }, window.refrenamerMessages); let clicked; mw.util.addPortletLink('p-tb', '#', messages.portlet, 't-refrenamer').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.refRenamer) { window.refRenamer(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript'); mw.notify(messages.loading, { autoHideSeconds: 'long', tag: 'refrenamer' }); }); }); if (['edit', 'submit'].includes(mw.config.get('wgAction'))) { mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/ExpandContractions.js&action=raw&ctype=text/javascript', 's'); mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/Unpipe.js&action=raw&ctype=text/javascript', 's'); } mw.config.get('wgAction') !== 'history' && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopyCodeBlock.js&action=raw&ctype=text/javascript', 's'); mw.config.exists('wgDiffNewId') && mw.config.get('wgDiscussionToolsFeaturesEnabled') && (function () { let data = {}, clickHandler, autoClear, run; window.dtc = data; let highlight = revId => { let ids = data[revId]; if (!ids || !ids.length) return; mw.loader.moduleRegistry['ext.discussionTools.init'].packageExports['highlighter.js'] .highlightNewComments(mw.dt.pageThreads, true, ids); if (clickHandler) { $(document.body).off('click', clickHandler); return; } $._data(document.body, 'events').click.some(o => { if (String(o.handler).includes('highlighter.clearHighlightTargetComment(')) { $(document.body).off('click', o.handler); clickHandler = o.handler; return true; } }); $._data(window, 'events').popstate.some(o => { if (String(o.handler).includes('highlighter.highlightTargetComment(')) { $(window).off('popstate', o.handler); return true; } }); }; let scroll = revId => { let ids = data[revId]; if (!ids || !ids.length) return; let yToSpan = Object.fromEntries( ids.map(id => document.getElementById(id)).filter(Boolean) .map(span => [span.getBoundingClientRect().y, span]) ); let ys = Object.keys(yToSpan); if (!ys.length) return; let lower = ys.filter(y => y > 10); if (!lower.length || Math.max(...lower) < document.documentElement.clientHeight ) { yToSpan[Math.min(...ys)].scrollIntoView(); } else { yToSpan[Math.min(...lower)].scrollIntoView(); } }; let scrollToNext = function (e) { e.preventDefault(); let revId = mw.config.get('wgDiffOldId'); if (!revId || !data[revId]) return; let i = data[revId].indexOf( this.closest('[data-mw-thread-id]').dataset.mwThreadId ); if (i === -1) return; let next = data[revId][i + 1] || data[revId][0]; document.getElementById(next).scrollIntoView(); }; mw.hook('wikipage.content').add(async $content => { let revId = mw.config.get('wgDiffOldId'); if (!revId) return; let param = new URLSearchParams(location.search).get('diffonly'); if (param && param !== '0') return; if (data[revId]) { highlight(revId); return; } await mw.loader.using(['ext.discussionTools.init', 'mediawiki.util']); let begin = Date.parse($('#mw-diff-otitle1 .mw-diff-timestamp').data('timestamp')); data[revId] = mw.dt.pageThreads.getCommentItems() .filter(c => c.timestamp > begin).map(c => c.id); if (!data[revId].length) return; await new Promise(setTimeout); highlight(revId); $content.find('.ext-discussiontools-init-replylink-buttons').filter(function () { return data[revId].includes(this.dataset.mwThreadId); }).children('span:last-of-type').before( ' | ', $('<a>').attr({ href: '#', role: 'button' }).text('next').on('click', scrollToNext) ); if (run || !document.getElementById('p-tb')) return; run = true; let portlet = mw.util.addPortletLink('p-tb', '#', 'Scroll to next', 't-scrolltonext'); portlet.firstElementChild.addEventListener('click', e => { e.preventDefault(); scroll(mw.config.get('wgDiffOldId')); }); mw.util.addPortletLink('p-tb', '#', 'Toggle highlight', 't-togglehighlight').firstElementChild.addEventListener('click', e => { e.preventDefault(); autoClear = !autoClear; if (autoClear) { $(document.body).on('click', clickHandler)[0].click(); } else { highlight(mw.config.get('wgDiffOldId')); } }); mw.loader.addStyleTag(`#t-scrolltonext{position:fixed;bottom:${portlet.clientHeight}px} #t-togglehighlight{position:fixed;bottom:0}`); }); }()); mw.config.get('wgNamespaceNumber') === 6 && mw.config.get('wgAction') === 'view' && mw.hook('wikipage.content').add($content => { $content.find('.filehistory .mw-usertoollinks-contribs').after(function () { return [ ' | ', $('<a>').attr('href', `${ mw.config.get('wgScript') }?title=Special:ListFiles/${ this.pathname.replace(/^.+\//, '') }&ilshowall=1`).text('uploads') ]; }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(function () { if (!$('input[name="wpSection"]').val()) return; mw.hook('wikipage.content').add(async $content => { let $refs = $content.find('.mw-ext-cite-warning-sectionpreview_no_text'); if (!$refs.length) return; let ids = {}; $refs.each(function () { ids[this.closest('[id]').id.replace(/-\d+$/, '')] = this; }); let response = await $.get(`/api/rest_v1/page/html/${encodeURIComponent(mw.config.get('wgPageName'))}`); $($.parseHTML(response)).find('.mw-reference-text').each(function () { ids[this.id.replace(/^mw-reference-text-|-\d+$/g, '')]?.replaceWith(this); }); }); }); mw.hook('moremenu.ready').add(config => { $('#mm-page-purge-cache > a').on('click', e => { e.preventDefault(); new mw.Api().post({ action: 'purge', forcelinkupdate: 1, titles: config.page.name, formatversion: 2 }).then(() => { location.href = mw.util.getUrl(); }); }); $('#mm-page-search-search-history-wikiblame > a').on('click', function (e) { e.preventDefault(); let q = prompt(); if (q === null) return; let href = this.href; if (q) { let removal = q[0] === '!'; if (removal) { q = q.slice(1); } href += '&needle=' + encodeURIComponent(q); if (removal) { href += '&binary_search_inverse=on'; } href += '&force_wikitags=on'; } open(href, '_blank'); }); $('#mm-page-expand-templates > a').on('click auxclick', function (e) { if (e.which > 2) return; e.preventDefault(); let revId = mw.config.get('wgRevisionId') || Number($('input[name=oldid]').val()); let url = revId ? '/w/rest.php/v1/revision/' + revId : '/w/rest.php/v1/page/' + config.page.encodedName; $.get(url).then(response => { $('<form>').attr({ method: 'post', action: this.href, target: '_blank' }).append( [ ['wpInput', response.source], ['wpContextTitle', config.page.name], ['wpRemoveComments', 1] ].map(([n, v]) => $('<input>').attr({ name: n, type: 'hidden' }).val(v)) ).appendTo(document.body).trigger('submit').remove(); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'ApiSandbox' && mw.hook('apisandbox.formatRequest').add((...args) => { args[4].complete = function () { setTimeout(() => { mw.hook('wikipage.content').fire($('.oo-ui-pageLayout-active')); }, 100); }; }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.storage')).then(async () => { let infuseAndCall = (query, method, ...args) => { let $widget = $(query); if ($widget.length) { return OO.ui.infuse($widget)[method](...args); } }; let section = $('input[name="wpSection"]').val(); if (section) { let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let save = () => { let newSource = $textarea.textSelection('getContents'); if (newSource === source) { mw.storage.session.remove('editfullpage'); } else { mw.storage.session.setObject('editfullpage', [ mw.config.get('wgPageName'), section, newSource.trimEnd(), infuseAndCall('#wpSummaryWidget', 'getValue') || '', Number(infuseAndCall('#wpMinoreditWidget', 'isSelected')) || 0, Number(infuseAndCall('#wpWatchthisWidget', 'isSelected')) || 0, infuseAndCall('#wpWatchlistExpiryWidget', 'getValue') || 'infinite' ]); } }; await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); setInterval(() => { mw.requestIdleCallback(save); }, 3000); window.addEventListener('beforeunload', save); return; } let data = mw.storage.session.getObject('editfullpage'); mw.storage.session.remove('editfullpage'); console.log(data); if (!data || data[0] !== mw.config.get('wgPageName')) return; let isNew = data[1] === 'new'; let isLead = data[1] === '0'; let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let newSource, start, msg, notifOpts = { autoHideSeconds: 'long' }; let orig = []; if (isNew) { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); newSource = source + (data[3] ? '\n== ' + data[3] + ' ==\n\n' : '\n') + data[2] + '\n'; start = source.length; } else { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core', 'mediawiki.api']); let { parse } = await new mw.Api().get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'sections', wrapoutputclass: '', disablelimitreport: 1, disableeditsection: 1, disabletoc: 1, formatversion: 2 }); let target = !isLead && parse.sections.find(s => s.index === data[1]); if (isLead || target) { let next = parse.sections.find(s => s.index - 1 === Number(data[1])); newSource = (isLead ? '' : [...source].slice(0, target.byteoffset)).join('') + data[2] + (next ? '\n\n' + [...source].slice(next.byteoffset).join('') : '\n'); start = isLead ? 0 : target.byteoffset; } else { newSource = source + '\n\n' + data[2] + '\n'; start = source.length; msg = `Section restored. Couldn't find the section. The source is appended at bottom.`; notifOpts.type = 'warn'; } orig[0] = infuseAndCall('#wpSummaryWidget', 'getValue'); infuseAndCall('#wpSummaryWidget', 'setValue', data[3]); } $textarea.textSelection('setContents', newSource); orig[1] = infuseAndCall('#wpMinoreditWidget', 'getSelected'); infuseAndCall('#wpMinoreditWidget', 'setSelected', data[4]); orig[2] = infuseAndCall('#wpWatchthisWidget', 'getSelected'); infuseAndCall('#wpWatchthisWidget', 'setSelected', data[5]); orig[3] = infuseAndCall('#wpWatchlistExpiryWidget', 'getValue'); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', data[6]); setTimeout(() => { $textarea.textSelection('setSelection', { start }); }); let notif = await mw.notify($([ document.createTextNode(msg || 'Section restored.'), $('<p>').append( new OO.ui.ButtonWidget({ flags: 'destructive', label: 'Discard' }).on('click', () => { $textarea.textSelection('setContents', source); if (orig[0]) { infuseAndCall('#wpSummaryWidget', 'setValue', orig[0]); } infuseAndCall('#wpMinoreditWidget', 'setSelected', orig[1]); infuseAndCall('#wpWatchthisWidget', 'setSelected', orig[2]); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', orig[3]); notif.close(); }).$element )[0] ]), notifOpts); }); mw.config.exists('wgPostEdit') && mw.loader.using('mediawiki.storage', () => { mw.storage.session.remove('editfullpage'); }); mw.config.get('wgAction') === 'history' && mw.hook('wikipage.content').add(async $content => { if (!$content.has('.mw-history-line-updated').length) return; let href = $content.find('a.mw-history-histlinks-current:not(.mw-history-line-updated a)').attr('href'); if (!href) { await mw.loader.using(['mediawiki.api', 'mediawiki.util']); let page = (await new mw.Api().get({ action: 'query', titles: mw.config.get('wgPageName'), prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev >= page.lastrevid) return; href = mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); } $content.find('.mw-history-compareselectedversions-button').first().after( ' ', $('<a>').attr({ class: 'unseendiff', href: href }).text('unseen') ); }); (async () => { let cspn = mw.config.get('wgCanonicalSpecialPageName'); let isBp = cspn === 'Blankpage'; if (!isBp && cspn !== 'Watchlist') return; await mw.loader.using('mediawiki.util'); let notify = async (text, options, pn) => { let msg = [document.createTextNode(text)]; if (pn) { msg.push( $('<p>').append( $('<a>').attr('href', mw.util.getUrl(pn)).text(pn), ' ', $('<span>').addClass('mw-changeslist-links').append( $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'edit' })) .text('edit') ), $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'history' })) .text('history') ) ) )[0] ); } if (isBp) { await $.ready; $('#mw-content-text').html(msg); } else { return mw.notify(msg, Object.assign(options || {}, { tag: 'unseendiff' })); } }; let getUrl = async pn => { await mw.loader.using('mediawiki.api'); let page = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; if (!page.notificationtimestamp) { notify(`Couldn't get the last seen time.`, { type: 'warn' }, pn); return; } let rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (rev === page.lastrevid) { notify('Already seen.', { type: 'warn' }, pn); return; } if (!rev) { rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, rvdir: 'newer', formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev) { notify(`Couldn't get the last seen revision.`, { type: 'warn' }, pn); return; } } if (rev > page.lastrevid) { notify(`Invalid rev for "${pn}" (rev: ${rev}, lastrevid: ${page.lastrevid})`, { autoHideSeconds: 'long', type: 'warn' }, pn); return; } return mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); }; if (isBp) { let pn = mw.config.get('wgTitle').match(/^[^/]+\/unseendiff\/(.+)$/)?.[1]; if (!pn) return; notify('Loading...', null, pn); let href = await getUrl(pn); if (!href) return; notify('Redirecting...', null, pn); location.href = href; return; } let handler = async function (e) { if (e.which > 2) return; e.preventDefault(); let pn = this.dataset.pn; if (!pn) { notify(`Couldn't get the page name.`, { type: 'error' }); return; } let notifPromise = notify('Loading...', { autoHideSeconds: 'long' }); let href = await getUrl(pn); if (!href) return; $(`.unseendiff-loader[data-pn="${$.escapeSelector(pn)}"]`).attr({ class: 'unseendiff', href: href, target: '_blank' }).off('click auxclick', handler); if (e.type === 'auxclick' || e.ctrlKey || e.metaKey || e.shiftKey) { open(href); } else { this.click(); } (await notifPromise).close(); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-changeslist-src-mw-edit.mw-changeslist-watchedunseen:not(.mw-changeslist-watchedseen) .mw-changeslist-line-inner' ).each(function () { let pn = this.dataset.targetPage || this.closest('[data-target-page]')?.dataset.targetPage || this.closest('table.mw-enhanced-rc')?.querySelector('[data-target-page]')?.dataset.targetPage; if (!pn) return; $('<span>').append( $('<a>').attr({ class: 'unseendiff-loader', href: mw.util.getUrl(`Special:BlankPage/unseendiff/${pn}`), 'data-pn': pn }).on('click auxclick', handler).text('unseen') ).appendTo( [...this.querySelectorAll('.mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); }); })(); ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.using('mediawiki.util', () => { let watched = new Set(); let query = async lis => { let titles = Object.keys(lis).slice(0, 50); if (!titles.length) return; await mw.loader.using('mediawiki.api'); let pages = (await new mw.Api().post({ action: 'query', titles: titles, prop: 'info', inprop: 'notificationtimestamp|watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages; for (let page of pages) { if (!Object.hasOwn(lis, page.title)) continue; if (page.watched) { watched.add(page); $(lis[page.title]).addClass('watched'); } if (!page.notificationtimestamp) continue; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev === page.lastrevid) continue; if (rev > page.lastrevid) { mw.notify($([ document.createTextNode('Invalid rev for "'), $('<a>').attr({ href: mw.util.getUrl(page.title, { action: 'history' }), target: '_blank' }).text(page.title)[0], document.createTextNode(`" (rev: ${rev}, lastrevid: ${page.lastrevid})`), ]), { autoHideSeconds: 'long', type: 'warn' }); continue; } $('<span>').append( $('<a>').attr({ class: 'unseendiff', href: mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }) }).text('unseen') ).appendTo( lis[page.title].map(li => ( [...li.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(li).before(' ') )) ); } titles.forEach(title => { delete lis[title]; }); query(lis); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-contributions-list > li:not(.mw-contributions-current)[data-mw-revid]' ).each(function () { let link = this.querySelector('a.mw-changeslist-date, a.mw-changeslist-history'); let pn = link ? new URLSearchParams(link.search).get('title') : ''; $('<span>').append( $('<a>').attr({ class: 'mw-changeslist-diff', href: mw.util.getUrl(pn, { diff: 'cur', oldid: this.dataset.mwRevid }) }).text('cur') ).appendTo( [...this.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); if (mw.config.get('wgWikiID') === 'wikidatawiki') return; let lis = {}; $content.find('.mw-contributions-title').each(function () { let title = this.textContent; if (!Object.hasOwn(lis, title)) { lis[title] = []; } lis[title].push(this.closest('li')); }); Object.keys(lis).forEach(title => { if (watched.has(title)) { $(lis[title]).addClass('watched'); delete lis[title]; } }); query(lis); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox9.js&action=raw&ctype=text/javascript'); mw.config.get('wgWikiID') === 'metawiki' && (async () => { let css = mw.loader.addStyleTag(`.wishtitle { font-size: 90%; font-style: italic; word-break: break-word; } .wishtitle > a { color: var(--color-warning, #886425); } .wishtitle > a:visited { color: var(--border-color-warning--hover, #735421); } .wishtitle-declined > a { text-decoration: line-through; } .wishtitle-declined > a:hover, .wishtitle-declined > a:focus, .mw-underline-always .wishtitle-declined > a { text-decoration: line-through underline; } #watchlist-edit-form .wishtitle { display: inline-block; } .mw-search-result-heading > .wishtitle, .catchangesviewer-table .wishtitle { display: block; } .catchangesviewer-table:has(.wishtitle) { white-space: wrap; }`); let lang = mw.config.get('wgUserLanguage'); let titles; let loadTitles = async () => { await mw.loader.using('mediawiki.storage'); titles = titles || mw.storage.getObject('wishtitles'); if (titles?.lang !== lang) { titles = { lang, w: [], fa: [] }; } }; let updateTitles = async (crwstatuses, crwcontinue) => { await mw.loader.using('mediawiki.api'); let params = { action: 'query', list: 'communityrequests-wishes', crwlang: lang, crwstatuses: crwstatuses, crwprop: 'title|updated', crwsort: 'updated', crwdir: 'ascending', crwlimit: 'max', crwcontinue: crwcontinue, formatversion: 2 }; if (!crwcontinue && !crwstatuses && titles._) { params.crwcontinue = `|${titles._}|0`; } let response = await new mw.Api().get(params); let wishes = response?.query?.['communityrequests-wishes']; if (wishes?.length) { let $span = $('<span>'); wishes.forEach(w => { let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1]; if (!id) return; titles.w[id - 1] = $span.html(w.title).text(); if (crwstatuses === 'declined') { (titles.wd = titles.wd || []).push(id - 1); } let faId = w.crfatitle?.match(/^Community Wishlist\/FA(\d+)/)?.[1]; if (!faId) return; titles.fa[faId - 1] = w.focusareatitle; }); if (!crwstatuses) { titles._ = wishes.at(-1).updated.replace(/\D/g, ''); } } let expiry = 86400; if (crwstatuses || crwcontinue) { let prev = mw.storage.getObject('_EXPIRY_wishtitles'); if (prev) { expiry = Math.round(Date.now() / 1000) + 86400 - prev; } } mw.storage.setObject('wishtitles', titles, expiry); crwcontinue = response?.continue?.crwcontinue; if (crwcontinue) { await updateTitles(crwstatuses, crwcontinue); } }; let getTitle = id => ( id[0] === 'W' ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1] ); let renderTitle = (title, id, tag = 'span') => { let classes = 'wishtitle'; if (id[0] === 'W' && titles.wd?.includes(id.slice(1) - 1)) { classes += ' wishtitle-declined'; } return $(`<${tag}>`).addClass(classes).append( $('<a>').attr({ href: `/wiki/Community_Wishlist/${id}`, title: `Community Wishlist/${id}` }).text(title) ); }; let callback = ([id, links]) => { let title = getTitle(id); if (!title) { return true; } $(links).after(' ', renderTitle(title, id)); }; let selector = '.mw-changeslist-title, ' + '.mw-changeslist-log-entry > a:not(.mw-userlink), ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' + '#watchlist-edit-form .cdx-table td > label > a, ' + '.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' + '.mw-contributions-title, ' + '#mw-whatlinkshere-list li > bdi > a, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-logevent-loglines > li > a, ' + '#mw-pages li > a, ' + '.catchangesviewer-table td:nth-child(3) > a'; mw.hook('wikipage.content').add(async $content => { let links = {}; $content.find('a').each(function () { if (!this.matches(selector)) return; let id = this.textContent.match( /^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/ )?.[1]; if (!id) return; (links[id] = links[id] || []).push(this); }); links = Object.entries(links); if (!links.length) return; await loadTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles('declined'); links.forEach(callback); }); let pn = mw.config.get('wgRelevantPageName'); let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1]; if (!id) return; await $.ready; let extTitle = document.querySelector('.ext-communityrequests-wish--title'); if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return; await loadTitles(); let title = getTitle(id); if (!title) { await updateTitles(); title = getTitle(id); if (!title) { await updateTitles('declined'); title = getTitle(id); if (!title) return; } } let $title = renderTitle(title, id, 'div'); if (mw.config.get('skin') === 'vector-2022') { $title.prependTo('.vector-page-toolbar'); } else { $title.insertAfter('#firstHeading'); } css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}'; if (extTitle) return; document.title = document.title.replace( pn.replaceAll('_', ' '), `${pn.replace(`Community_Wishlist/${id}`, title)} ($&)` ); })(); mw.config.get('wgWikiID') === 'metawiki' && mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => { if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return; let title = mw.config.get('wgTitle'); if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return; if (isWatched) { await new mw.Api().watch(title + '/Votes', expiry); mw.notify('Watching /Votes too.'); } else { await new mw.Api().unwatch(title + '/Votes'); mw.notify('Unwatched /Votes too.'); } }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && $(async () => { let $input = $('#wpTemplateSandboxTemplate'); if (!$input.length) return; mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}'); let makeTemplateField = () => new OO.ui.FieldLayout( new mw.widgets.TitleInputWidget({ inputId: 'wpTemplateSandboxTemplate', name: 'wpTemplateSandboxTemplate', showMissing: false, value: $input.val() }), { label: 'Template name:' } ); if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') { await mw.loader.using('mediawiki.widgets'); $input.parent().replaceWith(makeTemplateField().$element); return; } let require = await mw.loader.using([ 'ext.TemplateSandbox.TemplateSandboxTitleWidget', 'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options' ]); let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({ $overlay: true, id: 'wpTemplateSandboxPage', maxLength: 255, name: 'wpTemplateSandboxPage', placeholder: 'Page title', required: false, tabIndex: 10, templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val() }); widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}') .data('oouiInfused', widget); let fieldset = new OO.ui.FieldsetLayout({ classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'], id: 'templatesandbox-editform', items: [ makeTemplateField(), new OO.ui.ActionFieldLayout( widget, new OO.ui.ButtonInputWidget({ id: 'wpTemplateSandboxPreview', name: 'wpTemplateSandboxPreview', label: 'Show preview', tabIndex: 10, type: 'submit', useInputTag: true }), { align: 'top' } ) ], label: 'Preview page with this template' }); fieldset.$label.append('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await $.get( `https://en.wikipedia.org/w/rest.php/v1/page/${encodeURIComponent(xfd.page)}/html` ))); await queryTitles(xfd, xfd.titleExtractor($page)); }; let queryCat = async (xfd, gcmcontinue) => { let response = await new mw.Api().get({ action: 'query', prop: 'info|categories', inprop: 'watched', clprop: 'sortkey', clcategories: `Category:${xfd.cat}`, generator: 'categorymembers', gcmtitle: `Category:${xfd.cat}`, gcmlimit: 'max', gcmsort: 'timestamp', gcmdir: 'older', gcmcontinue: gcmcontinue, formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched && p.categories?.[0]?.sortkeyprefix !== ' ') { xfd.pages.push(p.title); } }); if (response?.continue?.gcmcontinue) { await queryCat(xfd, response.continue.gcmcontinue); } }; let show = async (xfd, lastId, isCache) => { if (xfd.portlet && isCache) return; let portletId = 'p-xfdnotifier-' + xfd.id; if (xfd.portlet) { $(xfd.portlet).find('ul').empty(); if (!xfd.pages.length) return; } else { await $.ready; xfd.portlet = mw.util.addPortlet(portletId, xfd.label, '#' + lastId); } let $label = $(`#${portletId}-label`).attr('title', xfd.full); if (xfd.page) { $label.wrapInner($('<a>').attr('href', mw.util.getUrl(xfd.page))); } xfd.pages.forEach(p => { let t = mw.Title.newFromText(p); let isTalk = t.isTalkPage(); let $other = $('<a>').attr({ href: t[isTalk ? 'getSubjectPage' : 'getTalkPage']().getUrl(), title: isTalk ? 'subject' : 'talk' }).text(isTalk ? 's' : 't'); let link = mw.util.addPortletLink(portletId, t.getUrl(), p).querySelector('a'); $('<span>').addClass('xfdnotifier-sublinks').append( $('<span>').append($other), $('<span>').append( $('<a>').attr({ href: t.getUrl({ action: 'history' }), title: 'history' }).text('h') ) ).insertAfter(link); }); }; mw.hook('wikipage.content').add(mw.util.throttle(async () => { let cache = mw.storage.getObject('xfdnotifier') || {}; let lastId = 'p-tb'; for (let xfd of xfds) { let portletId = 'p-xfdnotifier-' + xfd.id; let now = Math.floor(Date.now() / 1000); if (now - cache[xfd.id]?.[0] < 600) { xfd.pages = cache[xfd.id].slice(1); await show(xfd, lastId, true); lastId = portletId; continue; } xfd.pages = []; if (xfd.cat) { await queryCat(xfd); } else if (xfd.page) { await queryPage(xfd); } cache[xfd.id] = [now, ...xfd.pages]; mw.storage.setObject('xfdnotifier', cache, 604800); await show(xfd, lastId); lastId = portletId; } }, 1800000)); })(); 8d5v4ud7vrm7sldbwwzsi0gcdvm2niq 739831 739830 2026-04-30T06:36:36Z Nardog 40946 739831 javascript text/javascript (async function listTools() { let pageAction = mw.config.get('wgAction'); let isView = pageAction === 'view'; let isEdit = ['edit', 'submit'].includes(pageAction); if (!isView && !isEdit) return; let pageType = mw.config.get('wgCanonicalSpecialPageName') || mw.config.get('wgNamespaceNumber'); if (isView && !pageType && !mw.config.exists('wgRedirectedFrom') && !mw.config.get('wgIsRedirect') && !mw.config.get('wgPageName').includes('/') ) { return; } await mw.loader.using([ 'mediawiki.util', 'mediawiki.Title', 'mediawiki.api', 'mediawiki.interface.helpers.styles' ]); mw.loader.addStyleTag(`.listtools:not(#mw-content-subtitle .listtools) { font-size: 85%; } .listtools, .listtools a { font-weight: normal !important; font-style: normal; } .mw-datatable .listtools { display: block; } .listtools + .mw-whatlinkshere-tools, #watchlist-edit-form .listtools ~ .mw-changeslist-links, .mw-special-DisambiguationPageLinks .listtools + a { display: none; }`); let messages = Object.assign({ watched: 'Added "$1" to your watchlist', watchFail: `Couldn't watch "$1"`, unwatchFail: `Couldn't unwatch "$1"` }, window.listtoolsMessages); let getMsg = (key, ...args) => ( Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key ); let notif; let watchHandler = async function (e) { e.preventDefault(); let $link = $(this); let $wrapper = $link.parent(); $link.detach(); let params = new URLSearchParams(this.search); let action = params.get('action'); $wrapper.text(getMsg(action + 'ing')); let pn = params.get('title').replaceAll('_', ' '); let promise = new mw.Api()[action](pn); if (notif) { notif.close(); notif = null; } try { let result = await promise; if (!result || !result[action + 'ed']) throw ''; let newAction = action === 'watch' ? 'unwatch' : 'watch'; params.set('action', newAction); $link.add(`.listtools-watch > a[href="${this.pathname + this.search}"]`) .attr('href', this.pathname + '?' + params) .text(getMsg(newAction)); if (action !== 'watch') return; let require = await mw.loader.using([ 'mediawiki.notification', 'mediawiki.watchstar.widgets' ]); notif = await mw.notify( new (require('mediawiki.watchstar.widgets'))('watch', pn, null, $.noop, { message: getMsg('watched', pn) }).$element, { tag: 'listtools' } ); } catch { notif = await mw.notify(getMsg(action + 'Fail', pn), { tag: 'listtools', type: 'error' }); } finally { $wrapper.html($link); } }; let extGetMain = function () { return this.title; }; let re = new RegExp(`(?:\\?title=|${ mw.util.escapeRegExp(mw.format(mw.config.get('wgArticlePath'), '')) })([^#&?]+)`); let processed = new WeakSet(); let processLinks = ($links, module, titles) => { let isBatch = !!titles; titles = titles || new Set(); $links.each(function (i) { if (processed.has(this)) return; let $link = $links.eq(i); let pn; if (module.useText) { pn = $link.text(); } else { let match = $link.attr('href')?.match(re); if (!match) return; pn = decodeURIComponent(match[1]); } let t = mw.Title.newFromText(pn); if (!t) return; if (module.titlesOnly) { let text = $link.text(); if (text !== pn.replaceAll('_', ' ') && (text !== t.getMainText() || t.namespace === 2) ) { return; } } if ($link.is('.external, .extiw')) { Object.assign(t, { getMain: extGetMain, host: this.host, namespace: 0, title: pn }); } else { if (t.namespace < 0) return; if ($link.hasClass('new')) { t.missing = true; } titles.add(t.getSubjectPage().toText()); } let $tools = $('<span>').addClass('listtools mw-changeslist-links') .data('listtools', t); tools.forEach(tool => { addTool($tools, tool); }); if ($link.is(':is(del, bdi) > :only-child')) { if (module.position === 'end') { $link.parent().parent().append(' ', $tools); } else { $link.parent().after(' ', $tools); } } else if (module.position === 'end') { $link.parent().append(' ', $tools); } else { $link.after(' ', $tools); } if (module.post) { module.post($tools); } processed.add(this); }); if (!isBatch) { getWatched(titles); } }; let tools = [ { name: 'edit', url: t => t.getUrl({ action: 'edit' }) }, { name: 'hist', url: t => !t.missing && t.getUrl({ action: 'history' }) }, { name: 'links', url: t => mw.util.getUrl('Special:WhatLinksHere/' + t) }, { name: 'watch', url: t => !t.host && t.getSubjectPage().getUrl({ action: 'watch' }), callback: watchHandler } ]; let addTool = ($tools, tool, escapedName) => { let t = $tools.data('listtools'); let $duplicate = escapedName && $tools.children('.listtools-' + escapedName); let url = tool.url; if (typeof url === 'function') { url = url(t); if (!url) { $duplicate?.remove(); return; } } let $link = $('<a>').attr('href', url).text(getMsg(tool.name)); if (t.host) { $link.prop('host', t.host); } if (tool.callback) { $link.on('click', tool.callback); } let $wrapper = $('<span>').addClass('listtools-' + tool.name) .append($link); let $next = tool.next && $tools.children('.listtools-' + tool.next); if ($next?.length) { $duplicate?.remove(); $next.before($wrapper); } else if ($duplicate?.length) { $duplicate.replaceWith($wrapper); } else { $tools.append($wrapper); } }; let extend = tool => { if (tool.label && !Object.hasOwn(messages, tool.label)) { messages[tool.name] = tool.label; } if (tool.next) { tool.next = $.escapeSelector(tool.next); } let existingTool = tools.find(t => t.name === tool.name); if (existingTool) { Object.assign(existingTool, tool); } else { tools.push(tool); } let escapedName = existingTool && $.escapeSelector(tool.name); let $allTools = $('.listtools'); $allTools.each(function (i) { addTool($allTools.eq(i), tool, escapedName); }); }; let getWatched = async titles => { if (!Array.isArray(titles)) { titles = [...titles].slice(0, 500); } if (!titles.length) return; (await new mw.Api().post({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages.forEach(page => { if (!page.watched) return; $(`.listtools-watch > a[href="${mw.util.getUrl(page.title, { action: 'watch' })}"]`) .attr('href', mw.util.getUrl(page.title, { action: 'unwatch' })) .text(getMsg('unwatch')); }); getWatched(titles.slice(50)); }; mw.hook('listtools.ready').fire(extend); let catTreeCallback = (records, observer) => { let $links = $(records[0].target).find('.CategoryTreeItem > bdi > a'); if ($links.length) { observer.takeRecords(); observer.disconnect(); processLinks($links, catTreeModule); } }; let catTreeModule = { selector: '.CategoryTreeItem > bdi > a', types: [14, 'CategoryTree'], position: 'end', post: $tools => { $tools.parent().next('.CategoryTreeChildren').each(function () { new MutationObserver(catTreeCallback) .observe(this, { childList: true }); }); } }; let modules = [ { selector: '#mw-pages li > a, #mw-pages li > span > a', types: [14] }, catTreeModule, { selector: '#mw-imagepage-section-linkstoimage a, #mw-imagepage-section-globalusage a', types: [6] }, { selector: '#mw-globalusage-result a', types: ['GlobalUsage'] }, { selector: '.mw-search-result-heading > a, .searchalttitle > a.mw-redirect, .iw-result__title > a, .mw-search-exists a', types: ['Search'] }, { selector: '.mw-search-createlink a', types: ['Search'], titlesOnly: true }, { selector: '#watchlist-edit-form .cdx-table td > label > a', types: ['EditWatchlist'] }, { selector: '.plainlinks > li > a', types: ['AbuseLog'], titlesOnly: true }, { selector: '#mw-allmessagestable td:first-child > a:first-child:not(.new)', types: ['Allmessages'], position: 'end' }, { selector: '.mw-spcontent li a', types: ['DisambiguationPageLinks', 'Listredirects'], titlesOnly: true }, { selector: 'li > a:first-child', types: ['FileDuplicateSearch'] }, { selector: '.TablePager_col_title > a:first-child, .TablePager_col_template > a', types: ['LintErrors'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: 'form > ul > li > a', types: ['Nuke'], position: 'end', titlesOnly: true }, { selector: '.page-assessments a', types: ['PageAssessments'], titlesOnly: true }, { selector: '.TablePager_col_pr_page > a', types: ['Protectedpages'], position: 'end' }, { selector: '#mw-content-text > ul a', types: ['Protectedtitles'], position: 'end' }, { selector: '.mw-fr-pending-changes-page-title', types: ['PendingChanges'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: '#mw-content-text > ul a:first-child', types: ['StablePages'], position: 'end' }, { selector: '.TablePager_col__page a', types: ['TopicSubscriptions'] }, { selector: '.undeleteResult > a', types: ['Undelete'], position: 'end', useText: true }, { selector: '.TablePager_col_img_name > a:first-child', // types: ['Listfiles'], position: 'end' }, { selector: '.mw-newpages-pagename', post: $tools => { let $nodes = $tools.parent().contents(); $nodes.slice( $nodes.index($tools) + 1, $nodes.index($nodes.filter('.mw-newpages-length')) ).replaceWith(' '); } }, { selector: '#mw-whatlinkshere-list li > bdi > a' }, { selector: '.mw-changeslist-log-entry > a:not(.mw-changeslist-log-gblblock a, .mw-changeslist-log-globalauth a)', titlesOnly: true }, { selector: '.mw-logevent-loglines > li:not(.mw-logline-gblblock, .mw-logline-globalauth) > a', types: ['Log'], titlesOnly: true }, { selector: '#mw-diff-otitle1 > strong > a, #mw-diff-ntitle1 > strong > a', types: ['ComparePages'], position: 'end' }, { selector: '#movepage-oldlink, #movepage-newlink', types: ['Movepage'] }, { selector: '.mw-undelete-revision a:not(.mw-userlink, .mw-usertoollinks > a)', types: ['Undelete'], useText: true }, { selector: '.galleryfilename, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a' }, { selector: '.mw-spcontent li a', position: 'end', titlesOnly: true } ]; if (isEdit) { let post = $tools => { if (!$tools[0].closest('.templatesUsed')) return; $tools.parent().contents().last().each(function () { this.textContent = this.textContent.slice(1); }).end().slice(-3, -1).remove(); }; let callback = mw.util.debounce(() => { processLinks( $('.mw-editfooter-list a, #wikiPreview > .previewnote a'), { titlesOnly: true, post } ); }, 500); mw.hook('wikipage.editform').add($form => { callback(); $form.find('.templatesUsed').each(function () { if (processed.has(this)) return; processed.add(this); new MutationObserver(callback) .observe(this, { childList: true, subtree: true }); }); }); } else if (typeof pageType === 'number') { $(() => { processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {}); }); } mw.hook('wikipage.content').add($content => { let titles = new Set(); let $links = $content.find('a'); modules.forEach(module => { if (module.types && !module.types.includes(pageType)) return; processLinks($links.filter(module.selector), module, titles); }); getWatched(titles); }); }()); mw.hook('listtools.ready').add(extend => { // extend({ // name: 'talk', // url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(), // next: 'hist' // }); extend({ name: 'subject', url: t => t.isTalkPage() && t.getSubjectPage().getUrl(), next: 'hist' }); extend({ name: 'last', url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }), next: 'links' }); // extend({ // name: 'purge', // url: t => t.getUrl({ action: 'purge' }), // next: 'watch', // callback: function (e) { // e.preventDefault(); // let $link = $(this); // let $wrapper = $link.parent(); // $link.detach(); // $wrapper.text('purging'); // let pn = $wrapper.closest('.listtools').data('listtools').toText(); // new mw.Api().post({ // action: 'purge', // forcelinkupdate: 1, // titles: pn, // formatversion: 2 // }).then(response => { // if (response.purge[0].purged) { // mw.notify(`Purged "${pn}"'`); // } // }).always(() => { // $wrapper.html($link); // }); // } // }); extend({ name: 'copy', url: '#', callback: function (e) { e.preventDefault(); let text = $(this).closest('.listtools').data('listtools').toText(); let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch (err) {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } } }); }); (mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') && mw.config.get('wgCanonicalSpecialPageName') !== 'GlobalContributions' && (function consecudiff() { mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}'); let isHist = mw.config.get('wgAction') === 'history'; class Consecudiff { constructor(lis, isContribs) { this.isContribs = isContribs; this.isEnhanced = !isHist && !isContribs && lis[0].classList.contains('mw-enhanced-rc'); this.threshold = isContribs ? window.consecudiffContribsThreshold || 120 : isHist ? window.consecudiffHistThreshold || 720 : window.consecudiffThreshold || 720; this.strictMode = !isContribs && !!window.consecudiffDetectInterruptions; this.diffSelector = isHist ? 'a.mw-history-histlinks-previous' : '.mw-changeslist-diff'; this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' || (isHist || isContribs) && 'a.mw-changeslist-date'; this.hybridSelector = this.diffSelector; if (this.permaSelector) { this.hybridSelector += ', ' + this.permaSelector; } this.topClass = isContribs ? 'mw-contributions-current' : 'mw-changeslist-last'; let dependencies = ['mediawiki.util']; if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') { dependencies.push('mediawiki.language.months'); } mw.loader.using(dependencies, () => { let chunks; if (isHist) { chunks = this.chunkByUser(lis); } else { chunks = []; this.groupByTitle(lis).forEach(group => { chunks.push(...this.chunkByUser(group)); }); } let subchunks = []; chunks.forEach(chunk => { subchunks.push(...this.divideByDate(chunk)); }); let linkPairs = []; subchunks.forEach(subchunk => { linkPairs.push(...this.makeLinks(subchunk)); }); linkPairs.forEach(([$span, parent]) => { $span.appendTo(parent); }); }); } groupByTitle(lis) { let selector = this.isContribs ? '.mw-contributions-title' : '.mw-changeslist-title'; let lisByTitle = {}; lis.forEach(li => { let link = (this.isEnhanced ? li.closest('table') : li) .querySelector(selector); if (!link) return; let title = link.textContent; if (!lisByTitle.hasOwnProperty(title)) { lisByTitle[title] = []; } lisByTitle[title].push(li); }); return Object.values(lisByTitle).filter(group => group.length > 1); } chunkByUser(lis) { if (this.isSingleContribs) { return [lis]; } let chunks = [], lastSplitAt = 0, prevUser; this.isSingleContribs = lis.some((li, i) => { let link = li.querySelector('.mw-userlink'); if (!link && this.isContribs) { return true; } let user = link && link.textContent; if (!link || i && user !== prevUser) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevUser = user; }); if (this.isSingleContribs) { return [lis]; } chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } divideByDate(lis) { let chunks = [], lastSplitAt = 0, prevDate; lis.forEach((li, i) => { let date; if (isHist || this.isContribs) { date = this.parseDate( li.querySelector('.mw-changeslist-date').textContent ); } else { date = Date.parse( li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z') ); } if (date) { date = date / 60000; } if (i && prevDate - date > this.threshold) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevDate = date; if (!this.strictMode || lastSplitAt === i) return; let prevDiff = lis[i - 1].querySelector(this.diffSelector); if (prevDiff) { let prevNext = mw.util.getParamValue('oldid', prevDiff.search); if (prevNext !== li.dataset.mwRevid) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } } }); chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } makeLinks(lis) { let count = lis.length; let firstPerma; let start = lis.findIndex(li => ( firstPerma = li.querySelector(this.hybridSelector) )); if (start === -1 || count - start < 2) return []; let end, lastDiff; for (let i = count - 1; i > start; i--) { if (!isHist && !this.isContribs) { lastDiff = lis[i].querySelector(this.diffSelector); if (lastDiff || lis[i].classList.contains('mw-changeslist-src-mw-new') ) { end = i + 1; break; } } if (this.permaSelector && lis[i].querySelector(this.permaSelector)) { end = i + 1; break; } } if (!end) return []; count = end - start; let params = { diff: lis[start].dataset.mwRevid }; if (lastDiff) { params.oldid = mw.util.getParamValue('oldid', lastDiff.search); } else { params.oldid = lis[end - 1].dataset.mwRevid; if (isHist && lis[end - 1].querySelector(this.diffSelector) || this.isContribs && !lis[end - 1].querySelector('.newpage') ) { params.direction = 'prev'; } } let title = !isHist && mw.util.getParamValue('title', firstPerma.search); let url = mw.util.getUrl(title, params); let classes = 'consecudiff'; if (!isHist && lis[start].classList.contains(this.topClass)) { classes += ' consecudiff-top'; } return lis.slice(start, end).map((li, i) => [ $('<span>').addClass(classes).append( $('<a>') .attr('href', url) .text(this.convertNumber(count - i + '/' + count)) ), this.isEnhanced ? li.tagName === 'TR' ? li.lastElementChild : li.querySelector('.mw-changeslist-line-inner') : li ]); } parseDate(s) { let date = Date.parse(s); if (date) { return date; } if (s.includes(',')) date = Date.parse(s.replace(',', '')); if (date) { return date; } if (mw.loader.getState('mediawiki.language.months') !== 'ready') return; s = s.replace(/\D/g, c => { let n = mw.language.convertNumber(c, true); return Number.isNaN(n) ? c : n; }); let h, m; s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => { h = $1; m = $2; return ' '; }); if (!h) return; let y, dateFirst; s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => { y = $2; dateFirst = /\d/.test($1); return $1 + ' '; }); if (!y) return; let mo, d; if (dateFirst) { [d, s] = this.getDate(s); if (!d) return; [mo, s] = this.getMonth(s); if (mo === -1) return; } else { [mo, s] = this.getMonth(s); if (mo === -1) return; [d, s] = this.getDate(s); if (!d) return; } return new Date(y, mo, d, h, m).getTime(); } getMonth(s) { if (!this.months) { this.months = mw.language.months.abbrev .concat(mw.language.months.names, mw.language.months.genitive) .reverse(); } let mo = this.months.findIndex(mn => { let temp = s.replace(mn, ' '); if (temp !== s) { s = temp; return true; } }); if (mo === -1) { let [numeric, temp] = this.getDate(s); numeric = parseInt(numeric); if (numeric > 0 && numeric < 13) { mo = numeric - 1; s = temp; } } else { mo = 11 - mo % 12; } return [mo, s]; } getDate(s) { let d; s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => { d = $2; return $1 + ' '; }); return [d, s]; } convertNumber(num) { try { return mw.language.convertNumber(num); } catch (e) { return num; } } } mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-body').each(function () { let lis = this.querySelectorAll('.mw-contributions-list > li'); if (lis.length > 1) { new Consecudiff([...lis], !isHist); } }); if (isHist) return; let $lists = $content.filter('.mw-changeslist'); if (!$lists.length) { $lists = $content.find('.mw-changeslist'); } $lists.each(function () { let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]'); if (lis.length > 1) { new Consecudiff([...lis]); } }); }); }()); if (mw.config.get('wgNamespaceNumber') === 14 && ( mw.config.get('wgAction') === 'view' || !mw.config.get('wgArticleId') )) { mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.UserInputWidget', 'mediawiki.widgets.datetime', 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement', 'mediawiki.interface.helpers.styles', 'user.options' ]); } $(function moveHistory() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Move history', 't-movehistory').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.moveHistoryDialog) { window.moveHistoryDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter', 'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions', 'mediawiki.interface.helpers.styles' ]); }); }); }); $(function sectionSearch() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Section search', 't-sectionsearch').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.sectionSearchDialog) { window.sectionSearchDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows', 'mediawiki.widgets', 'mediawiki.widgets.NamespacesMultiselectWidget' ]); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth' && mw.loader.using('jquery.tablesorter', function sortCentralAuthByEditCount() { mw.hook('wikipage.content').add($content => { let $table = $content.find('.mw-centralauth-wikislist').has('td'); if (!$table.length) return; $table.tablesorter().data('tablesorter').sort([{ 4: 'desc' }, { 1: 'asc' }]); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && [10, 828].includes(mw.config.get('wgNamespaceNumber')) && !mw.config.get('wgTitle').endsWith('/doc') && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoTestcases.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/TemplatePreviewGuard.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // $(function templatePreviewGuard() { // let button = document.querySelector('input[name="wpTemplateSandboxPreview"]'); // if (!button) return; // let proceed; // button.addEventListener('click', e => { // if (proceed) { // proceed = false; // return; // } // e.preventDefault(); // e.stopPropagation(); // let formData = new FormData(button.form); // let page = formData.get('wpTemplateSandboxPage'); // let temp = formData.get('wpTemplateSandboxTemplate'); // if (!page || !temp) return; // mw.loader.using('mediawiki.api').then(() => ( // new mw.Api().get({ // action: 'query', // titles: page, // prop: 'templates', // tltemplates: temp, // formatversion: 2 // }) // )).always(response => { // if (((((response || {}).query || {}).pages || [])[0] || {}).templates || // confirm(`"${page}" doesn't appear to transclude "${temp}". Continue?`) // ) { // proceed = true; // button.click(); // } // }); // }, true); // if (!mw.config.get('wgArticleId')) return; // let widgetEl = document.querySelector('#wpTemplateSandboxPage.oo-ui-widget'); // if (!widgetEl) return; // let pn = mw.config.get('wgPageName').replace(/_/g, ' '); // mw.loader.using(['mediawiki.api', 'oojs-ui-core']).then(() => ( // new mw.Api().get({ // action: 'query', // titles: pn, // prop: 'transcludedin', // tiprop: 'title', // tilimit: 'max', // formatversion: 2 // }) // )).then(response => { // if (!response.batchcomplete) return; // let pages = response.query.pages[0].transcludedin // .filter(o => o.title !== pn); // if (!pages.length) return; // let widget = OO.ui.infuse(widgetEl); // if (pages.length === 1) { // widget.setValue(pages[0].title); // return; // } // widget.$element.replaceWith( // new OO.ui.ComboBoxInputWidget({ // id: 'wpTemplateSandboxPage', // maxlength: widget.$input.prop('maxLength'), // name: widget.$input.prop('name'), // options: pages // .sort((a, b) => a.ns - b.ns || -(a.title < b.title)) // .map(o => ({ data: o.title })), // placeholder: widget.$input.prop('placeholder'), // tabIndex: widget.getTabIndex(), // value: widget.getValue() // }).on('enter', e => { // e.preventDefault(); // button.click(); // }).$element // ); // }); // }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(async () => { let form = document.getElementById('editform'); if (!form) return; let formData = new FormData(form); let section = formData.get('wpSection'); if (section === 'new') return; let widget = document.getElementById('wpSummaryWidget'); if (!widget) return; let isOld = formData.get('altBaseRevId') > 0 || (formData.get('baseRevId') || formData.get('parentRevId')) !== formData.get('editRevId'); await mw.loader.using([ 'jquery.textSelection', 'mediawiki.util', 'mediawiki.api', 'oojs-ui-core', 'oojs-ui.styles.icons-editing-core' ]); let $textarea = $('#wpTextbox1'); let input = OO.ui.infuse(widget); let button = new OO.ui.ButtonWidget({ framed: false, icon: 'undo', classes: ['autosectionlink-button'], invisibleLabel: true, label: 'Restore previous section link' }).toggle().on('click', () => { let cache = button.getData(); input.setValue(input.getValue().replace( /^(\/\*.*?\*\/)?\s*/, cache[0] ? '/* ' + cache[0] + ' */ ' : '' )); updatePreview(cache[0]); cache.reverse(); }).on('toggle', () => { input.$input.css('width', `calc(100% - ${button.$element.width()}px)`); }); input.$input.after(button.$element); let update = mw.util.debounce($diff => { let lines = $textarea.textSelection('getContents').trimEnd().split('\n'); let firstLineNum, lastLineNum; if (isOld) { let i; $diff.find('td:last-child').each(function () { if (this.classList.contains('diff-lineno')) { i = this.textContent.replace(/\D+/g, '') - 1; } else if (this.classList.contains('diff-context')) { i++; } else if (this.classList.contains('diff-addedline')) { i++; if (!firstLineNum) { firstLineNum = i; } lastLineNum = i; } else if (this.classList.contains('diff-empty')) { if (!firstLineNum) { firstLineNum = i === 0 ? 1 : i; } lastLineNum = i; } }); } else { let origLines = $textarea.prop('defaultValue').trimEnd().split('\n'); firstLineNum = lines.findIndex((line, i) => line !== origLines[i]) + 1; if (!firstLineNum) { firstLineNum = lines.length < origLines.length ? lines.length : 1; } lastLineNum = lines.findLastIndex((line, i) => line !== origLines[i]) + 1; } let modLines = lines.slice(0, lastLineNum || (section ? 1 : 0)); let re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/; let lowest = 7; modLines.slice(firstLineNum).forEach(line => { let match = line.match(re); if (match?.[1].length < lowest) { lowest = match[1].length; } }); let head; modLines.slice(0, firstLineNum).reverse().some(line => { let match = line.match(re); if (match?.[1].length < lowest) { head = match[2]; return true; } }); if (head) { head = head .replace(/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>|<!--.*?-->|\[(?:https?:)?\/\/[^\s\[\]]+\s([^\]]+)\]/gi, '$1$2$3') .replace(/''(.+?)''/g, '$1') .trim(); } else if (modLines.length && lowest === 7 && section < 1 && ( section === '0' || lines.slice(modLines.length).some(line => re.test(line)) )) { head = ''; } let v = input.getValue(); let match = v.match(/^\/\*\s*(.+?)\s*\*\/\s*/); let prev = match?.[1]; if (prev === head) return; input.setValue(( head || head === '' ? '/* ' + head + ' */ ' : '' ) + (match ? v.slice(match[0].length) : v)); button.setData([prev, head]).toggle(true); updatePreview(head); }, 500); let updatePreview = head => { let $comment = $('.mw-summary-preview > .comment'); if (!$comment.length) return; let url; if (head) { url = mw.util.getUrl() + '#' + head.replace(/[\s_]+/g, '_'); } else if (head === '') { head = mw.messages.get('autocomment-top', '(top)'); url = mw.util.getUrl(); } let paren = [...mw.messages.get('parentheses', '($1)')][0]; let $nodes = $comment.contents(); let $ac = $nodes.eq(1); if ($nodes[0]?.textContent === paren && $ac.is('.autocomment:first-child')) { if (head) { $ac.children('a').attr('href', url).children('bdi').text(head); } else { if ($nodes[2]?.nodeType === 3) { $nodes[2].textContent = $nodes[2].textContent.trimStart(); } $ac.remove(); } } else if (head) { let rtl = document.body.classList.contains('sitedir-rtl'); $comment.prepend( paren, $('<span>').addClass('autocomment').append( $('<a>').attr({ href: url, title: mw.config.get('wgPageName').replaceAll('_', ' ') }).text(rtl ? '←' : '→').append( $('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(head) ), mw.messages.get('colon-separator', ': ') ) ); if ($nodes[0]?.nodeType === 3) { let text = $nodes[0].textContent; if (text.startsWith(paren)) { text = text.slice(paren.length); } $nodes[0].textContent = ' ' + text; } } }; if (isOld) { mw.hook('wikipage.diff').add(update); } else { $textarea.on('input', update); mw.hook('ext.CodeMirror.input').add(update); update(); } new mw.Api().loadMessagesIfMissing(['autocomment-top', 'colon-separator', 'parentheses']); mw.loader.addStyleTag('.autosectionlink-button{position:absolute;top:0;right:0;margin:0}'); }); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (function copyRevId() { let handler = function (e) { e.preventDefault(); let text = this.closest('.diff td')?.querySelector('[data-mw-revid]')?.dataset.mwRevid || this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!text) return; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); document.execCommand('copy'); $input.remove(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id') ) ); }); }()); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (() => { let handler = async function (e) { e.preventDefault(); let td = this.closest('.diff td'); let rev = td ? td.querySelector('[data-mw-revid]')?.dataset.mwRevid : this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!rev) { mw.notify(`Couldn't get the revision.`, { tag: 'markasunseen', type: 'error' }); return; } let pn = td ? new URLSearchParams([...td.querySelectorAll('a')].pop()?.search).get('title') : mw.config.get('wgPageName'); if (!pn) return; await mw.loader.using('mediawiki.api'); let result = (await new mw.Api().postWithEditToken({ action: 'setnotificationtimestamp', [td ? 'newerthanrevid' : 'torevid']: rev, titles: pn, formatversion: 2 })).setnotificationtimestamp?.[0]; if (Object.hasOwn(result, 'notificationtimestamp')) { mw.notify(`Marked revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'success' }); } else if (result?.notwatched) { mw.notify('This page is not on your watchlist.', { tag: 'markasunseen', type: 'warn' }); } else { mw.notify(`Couldn't mark revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions after this one as unseen' }).on('click', handler).text('unseen'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions since this one as unseen' }).on('click', handler).text('unseen') ) ); }); })(); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && ((mw.config.get('wgNamespaceNumber') % 2 || mw.config.get('wgNamespaceNumber') === 4) || (mw.config.get('wgWikiID') === 'metawiki' && mw.config.get('wgPageContentModel') === 'wikitext')) && mw.loader.using(['mediawiki.util', 'mediawiki.Title'], function copyUnsig() { let handler = function (e) { e.preventDefault(); let parent = this.closest('li, td'); let ts = parent.textContent.match(/\d\d:\d\d, \d\d? [A-Z][a-z]+ \d{4}/)?.[0]; if (!ts) return; let user = parent.querySelector('.mw-userlink').textContent; if (mw.util.isIPv6Address(user)) { user = user.toUpperCase(); } let temp = mw.util.isIPAddress(user) ? 'unsigned IP' : 'unsigned'; let text = `{{subst:${temp}|${user}|${ts}}}`; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').filter(function () { if (mw.config.get('wgWikiID') === 'metawiki') { return true; } let link = this.querySelector('strong > a') || this.parentElement.querySelector('#differences-prevlink, #differences-nextlink'); if (!link) return; let t = mw.Title.newFromText(mw.util.getParamValue('title', link.search)); return t.isTalkPage() || t.namespace === 4; }).append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig') ) ); }); }); // mw.config.get('wgAction') === 'history' && // mw.loader.using('mediawiki.util', function () { // mw.hook('wikipage.content').add($content => { // $content.find('a.mw-changeslist-date').after(function () { // return [ // ' (', // $('<a>').attr('href', mw.util.getUrl(null, { // action: 'edit', // oldid: this.closest('li').dataset.mwRevid // })).text('e'), // ')' // ]; // }); // }); // }); // ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && // mw.hook('wikipage.content').add($content => { // $content.find('.mw-changeslist-history').parent().after(function () { // return $('<span>').append( // $('<a>').attr( // 'href', // this.firstElementChild.getAttribute('href').slice(0, -7) + 'edit' // ).text('e') // ); // }); // }); if (screen.width < 500) { mw.loader.addStyleTag('@font-face{font-family:CharisW;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/017b2b2ad86e09d3c22b8cf0dfc78247/CharisSILRegular.ttf) format(truetype)} @font-face{font-family:CharisW;font-weight:700;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/6f5069ac6a300dad45383c952e92c573/CharisSILBold.ttf) format(truetype)} body .IPA{font-family:CharisW,sans-serif} .mw-highlight-lines > pre{width:120em}'); location.hash && $(() => { let target = document.querySelector(':target'); if (target?.getBoundingClientRect().top < 0) { target.scrollIntoView(); } }); } ['edit', 'submit'].includes(mw.config.get('wgAction')) && (mw.config.exists('wgCodeEditorCurrentLanguage') || mw.config.exists('cmMode') && mw.config.get('cmMode') !== 'mediawiki') && (function saveNEdit() { let notif; $(document.body).on('click', '#wpSave', async function (e) { if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey || e.originalEvent?.defaultPrevented ) { return; } e.preventDefault(); await mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'jquery.textSelection', 'oojs-ui-core' ]); let button = OO.ui.infuse(this.parentElement).setDisabled(true); let $textarea = $('#wpTextbox1'); let text = $textarea.textSelection('getContents'); let $summary = $('#wpSummary'); let formData = new FormData(this.form); let promise = new mw.Api().postWithEditToken({ action: 'edit', title: mw.config.get('wgPageName'), text: text, section: formData.get('wpSection') || undefined, summary: $summary.textSelection('getContents'), [$('#wpMinoredit').prop('checked') ? 'minor' : 'notminor']: 1, baserevid: formData.get('editRevId'), basetimestamp: formData.get('wpEdittime'), starttimestamp: formData.get('wpStarttime'), watchlist: $('#wpWatchthis').prop('checked') ? 'watch' : 'unwatch', watchlistexpiry: formData.get('wpWatchlistExpiry') || undefined, undo: formData.get('wpUndidRevision') || undefined, undoafter: formData.get('wpUndoAfter') || undefined, contentformat: formData.get('format'), contentmodel: formData.get('model'), assertuser: mw.config.get('wgUserName'), formatversion: 2 }); notif?.close(); notif = null; try { let response = await promise; if (response?.edit?.result !== 'Success') throw ''; $('#editform > input[name="wpUndidRevision"], #editform > input[name="wpUndoAfter"]').remove(); $textarea.data('origtext', text).prop('defaultValue', text); $summary.val($summary.prop('defaultValue')); if (mw.loader.getState('mediawiki.editRecovery.edit') === 'ready') { let storage = mw.loader.moduleRegistry['mediawiki.editRecovery.edit'].packageExports['storage.js']; storage.deleteData(mw.config.get('wgPageName')); storage.closeDatabase(); } notif = await mw.notify(response.edit.nochange ? 'No change' : [ document.createTextNode('Saved'), $('<p>').append( new OO.ui.ButtonWidget({ href: mw.util.getUrl(), target: '_blank', label: 'View' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { diff: response.edit.newrevid || 'cur', diffonly: 1 }), target: '_blank', label: 'Diff' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { action: 'history' }), target: '_blank', label: 'History' }).$element )[0] ], { tag: 'savenedit' }); } catch (error) { notif = await mw.notify(error?.error?.info || error || 'Save failed', { autoHideSeconds: 'long', tag: 'savenedit', type: 'error' }); } finally { button.setDisabled(); } }); }()); mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgAction') === 'view' && mw.config.get('wgCategories')?.some(c => c.endsWith(' actors') || c.endsWith(' actresses')) && $(() => { let n = $.escapeSelector(mw.config.get('wgTitle').replace(/ \(.+\)$/, '')); let $links = $(`.hatnote a[title$="${n} filmography"], .hatnote a[title*="${n} on "], .hatnote a[title*="${n} performances"]`); if (!$links.length) return; let titles = {}; $links = $links.filter(function () { let text = this.textContent; return !(titles[text] = Object.hasOwn(titles, text)); }); mw.notify( $links.length === 1 ? $links.clone() : $('<ul>').append($links.clone().wrap('<li>').parent()), { autoHideSeconds: 'long' } ); }); ['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/RCMuter.js&action=raw&ctype=text/javascript', 's'); location.hostname.endsWith('.wikipedia.org') && mw.config.get('wgNamespaceNumber') % 2 === 0 && // mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.util')).then(function refRenamer() { if (!document.getElementById('p-tb')) return; let messages = Object.assign({ portlet: 'RefRenamer', loading: 'Loading RefRenamer...' }, window.refrenamerMessages); let clicked; mw.util.addPortletLink('p-tb', '#', messages.portlet, 't-refrenamer').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.refRenamer) { window.refRenamer(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript'); mw.notify(messages.loading, { autoHideSeconds: 'long', tag: 'refrenamer' }); }); }); if (['edit', 'submit'].includes(mw.config.get('wgAction'))) { mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/ExpandContractions.js&action=raw&ctype=text/javascript', 's'); mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/Unpipe.js&action=raw&ctype=text/javascript', 's'); } mw.config.get('wgAction') !== 'history' && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopyCodeBlock.js&action=raw&ctype=text/javascript', 's'); mw.config.exists('wgDiffNewId') && mw.config.get('wgDiscussionToolsFeaturesEnabled') && (function () { let data = {}, clickHandler, autoClear, run; window.dtc = data; let highlight = revId => { let ids = data[revId]; if (!ids || !ids.length) return; mw.loader.moduleRegistry['ext.discussionTools.init'].packageExports['highlighter.js'] .highlightNewComments(mw.dt.pageThreads, true, ids); if (clickHandler) { $(document.body).off('click', clickHandler); return; } $._data(document.body, 'events').click.some(o => { if (String(o.handler).includes('highlighter.clearHighlightTargetComment(')) { $(document.body).off('click', o.handler); clickHandler = o.handler; return true; } }); $._data(window, 'events').popstate.some(o => { if (String(o.handler).includes('highlighter.highlightTargetComment(')) { $(window).off('popstate', o.handler); return true; } }); }; let scroll = revId => { let ids = data[revId]; if (!ids || !ids.length) return; let yToSpan = Object.fromEntries( ids.map(id => document.getElementById(id)).filter(Boolean) .map(span => [span.getBoundingClientRect().y, span]) ); let ys = Object.keys(yToSpan); if (!ys.length) return; let lower = ys.filter(y => y > 10); if (!lower.length || Math.max(...lower) < document.documentElement.clientHeight ) { yToSpan[Math.min(...ys)].scrollIntoView(); } else { yToSpan[Math.min(...lower)].scrollIntoView(); } }; let scrollToNext = function (e) { e.preventDefault(); let revId = mw.config.get('wgDiffOldId'); if (!revId || !data[revId]) return; let i = data[revId].indexOf( this.closest('[data-mw-thread-id]').dataset.mwThreadId ); if (i === -1) return; let next = data[revId][i + 1] || data[revId][0]; document.getElementById(next).scrollIntoView(); }; mw.hook('wikipage.content').add(async $content => { let revId = mw.config.get('wgDiffOldId'); if (!revId) return; let param = new URLSearchParams(location.search).get('diffonly'); if (param && param !== '0') return; if (data[revId]) { highlight(revId); return; } await mw.loader.using(['ext.discussionTools.init', 'mediawiki.util']); let begin = Date.parse($('#mw-diff-otitle1 .mw-diff-timestamp').data('timestamp')); data[revId] = mw.dt.pageThreads.getCommentItems() .filter(c => c.timestamp > begin).map(c => c.id); if (!data[revId].length) return; await new Promise(setTimeout); highlight(revId); $content.find('.ext-discussiontools-init-replylink-buttons').filter(function () { return data[revId].includes(this.dataset.mwThreadId); }).children('span:last-of-type').before( ' | ', $('<a>').attr({ href: '#', role: 'button' }).text('next').on('click', scrollToNext) ); if (run || !document.getElementById('p-tb')) return; run = true; let portlet = mw.util.addPortletLink('p-tb', '#', 'Scroll to next', 't-scrolltonext'); portlet.firstElementChild.addEventListener('click', e => { e.preventDefault(); scroll(mw.config.get('wgDiffOldId')); }); mw.util.addPortletLink('p-tb', '#', 'Toggle highlight', 't-togglehighlight').firstElementChild.addEventListener('click', e => { e.preventDefault(); autoClear = !autoClear; if (autoClear) { $(document.body).on('click', clickHandler)[0].click(); } else { highlight(mw.config.get('wgDiffOldId')); } }); mw.loader.addStyleTag(`#t-scrolltonext{position:fixed;bottom:${portlet.clientHeight}px} #t-togglehighlight{position:fixed;bottom:0}`); }); }()); mw.config.get('wgNamespaceNumber') === 6 && mw.config.get('wgAction') === 'view' && mw.hook('wikipage.content').add($content => { $content.find('.filehistory .mw-usertoollinks-contribs').after(function () { return [ ' | ', $('<a>').attr('href', `${ mw.config.get('wgScript') }?title=Special:ListFiles/${ this.pathname.replace(/^.+\//, '') }&ilshowall=1`).text('uploads') ]; }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(function () { if (!$('input[name="wpSection"]').val()) return; mw.hook('wikipage.content').add(async $content => { let $refs = $content.find('.mw-ext-cite-warning-sectionpreview_no_text'); if (!$refs.length) return; let ids = {}; $refs.each(function () { ids[this.closest('[id]').id.replace(/-\d+$/, '')] = this; }); let response = await $.get(`/api/rest_v1/page/html/${encodeURIComponent(mw.config.get('wgPageName'))}`); $($.parseHTML(response)).find('.mw-reference-text').each(function () { ids[this.id.replace(/^mw-reference-text-|-\d+$/g, '')]?.replaceWith(this); }); }); }); mw.hook('moremenu.ready').add(config => { $('#mm-page-purge-cache > a').on('click', e => { e.preventDefault(); new mw.Api().post({ action: 'purge', forcelinkupdate: 1, titles: config.page.name, formatversion: 2 }).then(() => { location.href = mw.util.getUrl(); }); }); $('#mm-page-search-search-history-wikiblame > a').on('click', function (e) { e.preventDefault(); let q = prompt(); if (q === null) return; let href = this.href; if (q) { let removal = q[0] === '!'; if (removal) { q = q.slice(1); } href += '&needle=' + encodeURIComponent(q); if (removal) { href += '&binary_search_inverse=on'; } href += '&force_wikitags=on'; } open(href, '_blank'); }); $('#mm-page-expand-templates > a').on('click auxclick', function (e) { if (e.which > 2) return; e.preventDefault(); let revId = mw.config.get('wgRevisionId') || Number($('input[name=oldid]').val()); let url = revId ? '/w/rest.php/v1/revision/' + revId : '/w/rest.php/v1/page/' + config.page.encodedName; $.get(url).then(response => { $('<form>').attr({ method: 'post', action: this.href, target: '_blank' }).append( [ ['wpInput', response.source], ['wpContextTitle', config.page.name], ['wpRemoveComments', 1] ].map(([n, v]) => $('<input>').attr({ name: n, type: 'hidden' }).val(v)) ).appendTo(document.body).trigger('submit').remove(); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'ApiSandbox' && mw.hook('apisandbox.formatRequest').add((...args) => { args[4].complete = function () { setTimeout(() => { mw.hook('wikipage.content').fire($('.oo-ui-pageLayout-active')); }, 100); }; }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.storage')).then(async () => { let infuseAndCall = (query, method, ...args) => { let $widget = $(query); if ($widget.length) { return OO.ui.infuse($widget)[method](...args); } }; let section = $('input[name="wpSection"]').val(); if (section) { let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let save = () => { let newSource = $textarea.textSelection('getContents'); if (newSource === source) { mw.storage.session.remove('editfullpage'); } else { mw.storage.session.setObject('editfullpage', [ mw.config.get('wgPageName'), section, newSource.trimEnd(), infuseAndCall('#wpSummaryWidget', 'getValue') || '', Number(infuseAndCall('#wpMinoreditWidget', 'isSelected')) || 0, Number(infuseAndCall('#wpWatchthisWidget', 'isSelected')) || 0, infuseAndCall('#wpWatchlistExpiryWidget', 'getValue') || 'infinite' ]); } }; await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); setInterval(() => { mw.requestIdleCallback(save); }, 3000); window.addEventListener('beforeunload', save); return; } let data = mw.storage.session.getObject('editfullpage'); mw.storage.session.remove('editfullpage'); console.log(data); if (!data || data[0] !== mw.config.get('wgPageName')) return; let isNew = data[1] === 'new'; let isLead = data[1] === '0'; let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let newSource, start, msg, notifOpts = { autoHideSeconds: 'long' }; let orig = []; if (isNew) { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); newSource = source + (data[3] ? '\n== ' + data[3] + ' ==\n\n' : '\n') + data[2] + '\n'; start = source.length; } else { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core', 'mediawiki.api']); let { parse } = await new mw.Api().get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'sections', wrapoutputclass: '', disablelimitreport: 1, disableeditsection: 1, disabletoc: 1, formatversion: 2 }); let target = !isLead && parse.sections.find(s => s.index === data[1]); if (isLead || target) { let next = parse.sections.find(s => s.index - 1 === Number(data[1])); newSource = (isLead ? '' : [...source].slice(0, target.byteoffset)).join('') + data[2] + (next ? '\n\n' + [...source].slice(next.byteoffset).join('') : '\n'); start = isLead ? 0 : target.byteoffset; } else { newSource = source + '\n\n' + data[2] + '\n'; start = source.length; msg = `Section restored. Couldn't find the section. The source is appended at bottom.`; notifOpts.type = 'warn'; } orig[0] = infuseAndCall('#wpSummaryWidget', 'getValue'); infuseAndCall('#wpSummaryWidget', 'setValue', data[3]); } $textarea.textSelection('setContents', newSource); orig[1] = infuseAndCall('#wpMinoreditWidget', 'getSelected'); infuseAndCall('#wpMinoreditWidget', 'setSelected', data[4]); orig[2] = infuseAndCall('#wpWatchthisWidget', 'getSelected'); infuseAndCall('#wpWatchthisWidget', 'setSelected', data[5]); orig[3] = infuseAndCall('#wpWatchlistExpiryWidget', 'getValue'); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', data[6]); setTimeout(() => { $textarea.textSelection('setSelection', { start }); }); let notif = await mw.notify($([ document.createTextNode(msg || 'Section restored.'), $('<p>').append( new OO.ui.ButtonWidget({ flags: 'destructive', label: 'Discard' }).on('click', () => { $textarea.textSelection('setContents', source); if (orig[0]) { infuseAndCall('#wpSummaryWidget', 'setValue', orig[0]); } infuseAndCall('#wpMinoreditWidget', 'setSelected', orig[1]); infuseAndCall('#wpWatchthisWidget', 'setSelected', orig[2]); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', orig[3]); notif.close(); }).$element )[0] ]), notifOpts); }); mw.config.exists('wgPostEdit') && mw.loader.using('mediawiki.storage', () => { mw.storage.session.remove('editfullpage'); }); mw.config.get('wgAction') === 'history' && mw.hook('wikipage.content').add(async $content => { if (!$content.has('.mw-history-line-updated').length) return; let href = $content.find('a.mw-history-histlinks-current:not(.mw-history-line-updated a)').attr('href'); if (!href) { await mw.loader.using(['mediawiki.api', 'mediawiki.util']); let page = (await new mw.Api().get({ action: 'query', titles: mw.config.get('wgPageName'), prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev >= page.lastrevid) return; href = mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); } $content.find('.mw-history-compareselectedversions-button').first().after( ' ', $('<a>').attr({ class: 'unseendiff', href: href }).text('unseen') ); }); (async () => { let cspn = mw.config.get('wgCanonicalSpecialPageName'); let isBp = cspn === 'Blankpage'; if (!isBp && cspn !== 'Watchlist') return; await mw.loader.using('mediawiki.util'); let notify = async (text, options, pn) => { let msg = [document.createTextNode(text)]; if (pn) { msg.push( $('<p>').append( $('<a>').attr('href', mw.util.getUrl(pn)).text(pn), ' ', $('<span>').addClass('mw-changeslist-links').append( $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'edit' })) .text('edit') ), $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'history' })) .text('history') ) ) )[0] ); } if (isBp) { await $.ready; $('#mw-content-text').html(msg); } else { return mw.notify(msg, Object.assign(options || {}, { tag: 'unseendiff' })); } }; let getUrl = async pn => { await mw.loader.using('mediawiki.api'); let page = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; if (!page.notificationtimestamp) { notify(`Couldn't get the last seen time.`, { type: 'warn' }, pn); return; } let rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (rev === page.lastrevid) { notify('Already seen.', { type: 'warn' }, pn); return; } if (!rev) { rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, rvdir: 'newer', formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev) { notify(`Couldn't get the last seen revision.`, { type: 'warn' }, pn); return; } } if (rev > page.lastrevid) { notify(`Invalid rev for "${pn}" (rev: ${rev}, lastrevid: ${page.lastrevid})`, { autoHideSeconds: 'long', type: 'warn' }, pn); return; } return mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); }; if (isBp) { let pn = mw.config.get('wgTitle').match(/^[^/]+\/unseendiff\/(.+)$/)?.[1]; if (!pn) return; notify('Loading...', null, pn); let href = await getUrl(pn); if (!href) return; notify('Redirecting...', null, pn); location.href = href; return; } let handler = async function (e) { if (e.which > 2) return; e.preventDefault(); let pn = this.dataset.pn; if (!pn) { notify(`Couldn't get the page name.`, { type: 'error' }); return; } let notifPromise = notify('Loading...', { autoHideSeconds: 'long' }); let href = await getUrl(pn); if (!href) return; $(`.unseendiff-loader[data-pn="${$.escapeSelector(pn)}"]`).attr({ class: 'unseendiff', href: href, target: '_blank' }).off('click auxclick', handler); if (e.type === 'auxclick' || e.ctrlKey || e.metaKey || e.shiftKey) { open(href); } else { this.click(); } (await notifPromise).close(); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-changeslist-src-mw-edit.mw-changeslist-watchedunseen:not(.mw-changeslist-watchedseen) .mw-changeslist-line-inner' ).each(function () { let pn = this.dataset.targetPage || this.closest('[data-target-page]')?.dataset.targetPage || this.closest('table.mw-enhanced-rc')?.querySelector('[data-target-page]')?.dataset.targetPage; if (!pn) return; $('<span>').append( $('<a>').attr({ class: 'unseendiff-loader', href: mw.util.getUrl(`Special:BlankPage/unseendiff/${pn}`), 'data-pn': pn }).on('click auxclick', handler).text('unseen') ).appendTo( [...this.querySelectorAll('.mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); }); })(); ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.using('mediawiki.util', () => { let watched = new Set(); let query = async lis => { let titles = Object.keys(lis).slice(0, 50); if (!titles.length) return; await mw.loader.using('mediawiki.api'); let pages = (await new mw.Api().post({ action: 'query', titles: titles, prop: 'info', inprop: 'notificationtimestamp|watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages; for (let page of pages) { if (!Object.hasOwn(lis, page.title)) continue; if (page.watched) { watched.add(page); $(lis[page.title]).addClass('watched'); } if (!page.notificationtimestamp) continue; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev === page.lastrevid) continue; if (rev > page.lastrevid) { mw.notify($([ document.createTextNode('Invalid rev for "'), $('<a>').attr({ href: mw.util.getUrl(page.title, { action: 'history' }), target: '_blank' }).text(page.title)[0], document.createTextNode(`" (rev: ${rev}, lastrevid: ${page.lastrevid})`), ]), { autoHideSeconds: 'long', type: 'warn' }); continue; } $('<span>').append( $('<a>').attr({ class: 'unseendiff', href: mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }) }).text('unseen') ).appendTo( lis[page.title].map(li => ( [...li.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(li).before(' ') )) ); } titles.forEach(title => { delete lis[title]; }); query(lis); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-contributions-list > li:not(.mw-contributions-current)[data-mw-revid]' ).each(function () { let link = this.querySelector('a.mw-changeslist-date, a.mw-changeslist-history'); let pn = link ? new URLSearchParams(link.search).get('title') : ''; $('<span>').append( $('<a>').attr({ class: 'mw-changeslist-diff', href: mw.util.getUrl(pn, { diff: 'cur', oldid: this.dataset.mwRevid }) }).text('cur') ).appendTo( [...this.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); if (mw.config.get('wgWikiID') === 'wikidatawiki') return; let lis = {}; $content.find('.mw-contributions-title').each(function () { let title = this.textContent; if (!Object.hasOwn(lis, title)) { lis[title] = []; } lis[title].push(this.closest('li')); }); Object.keys(lis).forEach(title => { if (watched.has(title)) { $(lis[title]).addClass('watched'); delete lis[title]; } }); query(lis); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox9.js&action=raw&ctype=text/javascript'); mw.config.get('wgWikiID') === 'metawiki' && (async () => { let css = mw.loader.addStyleTag(`.wishtitle { font-size: 90%; font-style: italic; word-break: break-word; } .wishtitle > a { color: var(--color-warning, #886425); } .wishtitle > a:visited { color: var(--border-color-warning--hover, #735421); } .wishtitle-declined > a { text-decoration: line-through; } .wishtitle-declined > a:hover, .wishtitle-declined > a:focus, .mw-underline-always .wishtitle-declined > a { text-decoration: line-through underline; } #watchlist-edit-form .wishtitle { display: inline-block; } .mw-search-result-heading > .wishtitle, .catchangesviewer-table .wishtitle { display: block; } .catchangesviewer-table:has(.wishtitle) { white-space: wrap; }`); let lang = mw.config.get('wgUserLanguage'); let titles; let loadTitles = async () => { await mw.loader.using('mediawiki.storage'); titles = titles || mw.storage.getObject('wishtitles'); if (titles?.lang !== lang) { titles = { lang, w: [], fa: [] }; } }; let updateTitles = async (crwstatuses, crwcontinue) => { await mw.loader.using('mediawiki.api'); let params = { action: 'query', list: 'communityrequests-wishes', crwlang: lang, crwstatuses: crwstatuses, crwprop: 'title|updated', crwsort: 'updated', crwdir: 'ascending', crwlimit: 'max', crwcontinue: crwcontinue, formatversion: 2 }; if (!crwcontinue && !crwstatuses && titles._) { params.crwcontinue = `|${titles._}|0`; } let response = await new mw.Api().get(params); let wishes = response?.query?.['communityrequests-wishes']; if (wishes?.length) { let $span = $('<span>'); wishes.forEach(w => { let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1]; if (!id) return; titles.w[id - 1] = $span.html(w.title).text(); if (crwstatuses === 'declined') { (titles.wd = titles.wd || []).push(id - 1); } let faId = w.crfatitle?.match(/^Community Wishlist\/FA(\d+)/)?.[1]; if (!faId) return; titles.fa[faId - 1] = w.focusareatitle; }); if (!crwstatuses) { titles._ = wishes.at(-1).updated.replace(/\D/g, ''); } } let expiry = 86400; if (crwstatuses || crwcontinue) { let prev = mw.storage.getObject('_EXPIRY_wishtitles'); if (prev) { expiry = Math.round(Date.now() / 1000) + 86400 - prev; } } mw.storage.setObject('wishtitles', titles, expiry); crwcontinue = response?.continue?.crwcontinue; if (crwcontinue) { await updateTitles(crwstatuses, crwcontinue); } }; let getTitle = id => ( id[0] === 'W' ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1] ); let renderTitle = (title, id, tag = 'span') => { let classes = 'wishtitle'; if (id[0] === 'W' && titles.wd?.includes(id.slice(1) - 1)) { classes += ' wishtitle-declined'; } return $(`<${tag}>`).addClass(classes).append( $('<a>').attr({ href: `/wiki/Community_Wishlist/${id}`, title: `Community Wishlist/${id}` }).text(title) ); }; let callback = ([id, links]) => { let title = getTitle(id); if (!title) { return true; } $(links).after(' ', renderTitle(title, id)); }; let selector = '.mw-changeslist-title, ' + '.mw-changeslist-log-entry > a:not(.mw-userlink), ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' + '#watchlist-edit-form .cdx-table td > label > a, ' + '.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' + '.mw-contributions-title, ' + '#mw-whatlinkshere-list li > bdi > a, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-logevent-loglines > li > a, ' + '#mw-pages li > a, ' + '.catchangesviewer-table td:nth-child(3) > a'; mw.hook('wikipage.content').add(async $content => { let links = {}; $content.find('a').each(function () { if (!this.matches(selector)) return; let id = this.textContent.match( /^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/ )?.[1]; if (!id) return; (links[id] = links[id] || []).push(this); }); links = Object.entries(links); if (!links.length) return; await loadTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles('declined'); links.forEach(callback); }); let pn = mw.config.get('wgRelevantPageName'); let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1]; if (!id) return; await $.ready; let extTitle = document.querySelector('.ext-communityrequests-wish--title'); if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return; await loadTitles(); let title = getTitle(id); if (!title) { await updateTitles(); title = getTitle(id); if (!title) { await updateTitles('declined'); title = getTitle(id); if (!title) return; } } let $title = renderTitle(title, id, 'div'); if (mw.config.get('skin') === 'vector-2022') { $title.prependTo('.vector-page-toolbar'); } else { $title.insertAfter('#firstHeading'); } css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}'; if (extTitle) return; document.title = document.title.replace( pn.replaceAll('_', ' '), `${pn.replace(`Community_Wishlist/${id}`, title)} ($&)` ); })(); mw.config.get('wgWikiID') === 'metawiki' && mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => { if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return; let title = mw.config.get('wgTitle'); if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return; if (isWatched) { await new mw.Api().watch(title + '/Votes', expiry); mw.notify('Watching /Votes too.'); } else { await new mw.Api().unwatch(title + '/Votes'); mw.notify('Unwatched /Votes too.'); } }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && $(async () => { let $input = $('#wpTemplateSandboxTemplate'); if (!$input.length) return; mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}'); let makeTemplateField = () => new OO.ui.FieldLayout( new mw.widgets.TitleInputWidget({ inputId: 'wpTemplateSandboxTemplate', name: 'wpTemplateSandboxTemplate', showMissing: false, value: $input.val() }), { label: 'Template name:' } ); if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') { await mw.loader.using('mediawiki.widgets'); $input.parent().replaceWith(makeTemplateField().$element); return; } let require = await mw.loader.using([ 'ext.TemplateSandbox.TemplateSandboxTitleWidget', 'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options' ]); let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({ $overlay: true, id: 'wpTemplateSandboxPage', maxLength: 255, name: 'wpTemplateSandboxPage', placeholder: 'Page title', required: false, tabIndex: 10, templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val() }); widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}') .data('oouiInfused', widget); let fieldset = new OO.ui.FieldsetLayout({ classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'], id: 'templatesandbox-editform', items: [ makeTemplateField(), new OO.ui.ActionFieldLayout( widget, new OO.ui.ButtonInputWidget({ id: 'wpTemplateSandboxPreview', name: 'wpTemplateSandboxPreview', label: 'Show preview', tabIndex: 10, type: 'submit', useInputTag: true }), { align: 'top' } ) ], label: 'Preview page with this template' }); fieldset.$label.append('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await $.get( `https://en.wikipedia.org/w/rest.php/v1/page/${encodeURIComponent(xfd.page)}/html` ))); await queryTitles(xfd, xfd.titleExtractor($page)); }; let queryCat = async (xfd, gcmcontinue) => { let response = await new mw.Api().get({ action: 'query', prop: 'info|categories', inprop: 'watched', clprop: 'sortkey', clcategories: `Category:${xfd.cat}`, generator: 'categorymembers', gcmtitle: `Category:${xfd.cat}`, gcmlimit: 'max', gcmsort: 'timestamp', gcmdir: 'older', gcmcontinue: gcmcontinue, formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched && p.categories?.[0]?.sortkeyprefix !== ' ') { xfd.pages.push(p.title); } }); if (response?.continue?.gcmcontinue) { await queryCat(xfd, response.continue.gcmcontinue); } }; let show = async (xfd, lastId, isCache) => { if (xfd.portlet && isCache) return; let portletId = 'p-xfdnotifier-' + xfd.id; if (xfd.portlet) { $(xfd.portlet).find('ul').empty(); if (!xfd.pages.length) return; } else { await $.ready; xfd.portlet = mw.util.addPortlet(portletId, xfd.label, '#' + lastId); } let $label = $(`#${portletId}-label`).attr('title', xfd.full); if (xfd.page) { $label.wrapInner($('<a>').attr('href', mw.util.getUrl(xfd.page))); } xfd.pages.forEach(p => { let t = mw.Title.newFromText(p); let isTalk = t.isTalkPage(); let $other = $('<a>').attr({ href: t[isTalk ? 'getSubjectPage' : 'getTalkPage']().getUrl(), title: isTalk ? 'subject' : 'talk' }).text(isTalk ? 's' : 't'); let link = mw.util.addPortletLink(portletId, t.getUrl(), p).querySelector('a'); $('<span>').addClass('xfdnotifier-sublinks').append( $('<span>').append($other), $('<span>').append( $('<a>').attr({ href: t.getUrl({ action: 'history' }), title: 'history' }).text('h') ) ).insertAfter(link); }); }; mw.hook('wikipage.content').add(mw.util.throttle(async () => { let cache = mw.storage.getObject('xfdnotifier') || {}; let lastId = 'p-tb'; for (let xfd of xfds) { let portletId = 'p-xfdnotifier-' + xfd.id; let now = Math.floor(Date.now() / 1000); if (now - cache[xfd.id]?.[0] < 600) { xfd.pages = cache[xfd.id].slice(1); await show(xfd, lastId, true); lastId = portletId; continue; } xfd.pages = []; if (xfd.cat) { await queryCat(xfd); } else if (xfd.page) { await queryPage(xfd); } cache[xfd.id] = [now, ...xfd.pages]; mw.storage.setObject('xfdnotifier', cache, 604800); await show(xfd, lastId); lastId = portletId; } }, 1800000)); })(); nv5buvj60e2is5csc86wogs25hguuhq 739832 739831 2026-04-30T06:37:50Z Nardog 40946 739832 javascript text/javascript (async function listTools() { let pageAction = mw.config.get('wgAction'); let isView = pageAction === 'view'; let isEdit = ['edit', 'submit'].includes(pageAction); if (!isView && !isEdit) return; let pageType = mw.config.get('wgCanonicalSpecialPageName') || mw.config.get('wgNamespaceNumber'); if (isView && !pageType && !mw.config.exists('wgRedirectedFrom') && !mw.config.get('wgIsRedirect') && !mw.config.get('wgPageName').includes('/') ) { return; } await mw.loader.using([ 'mediawiki.util', 'mediawiki.Title', 'mediawiki.api', 'mediawiki.interface.helpers.styles' ]); mw.loader.addStyleTag(`.listtools:not(#mw-content-subtitle .listtools) { font-size: 85%; } .listtools, .listtools a { font-weight: normal !important; font-style: normal; } .mw-datatable .listtools { display: block; } .listtools + .mw-whatlinkshere-tools, #watchlist-edit-form .listtools ~ .mw-changeslist-links, .mw-special-DisambiguationPageLinks .listtools + a { display: none; }`); let messages = Object.assign({ watched: 'Added "$1" to your watchlist', watchFail: `Couldn't watch "$1"`, unwatchFail: `Couldn't unwatch "$1"` }, window.listtoolsMessages); let getMsg = (key, ...args) => ( Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key ); let notif; let watchHandler = async function (e) { e.preventDefault(); let $link = $(this); let $wrapper = $link.parent(); $link.detach(); let params = new URLSearchParams(this.search); let action = params.get('action'); $wrapper.text(getMsg(action + 'ing')); let pn = params.get('title').replaceAll('_', ' '); let promise = new mw.Api()[action](pn); if (notif) { notif.close(); notif = null; } try { let result = await promise; if (!result || !result[action + 'ed']) throw ''; let newAction = action === 'watch' ? 'unwatch' : 'watch'; params.set('action', newAction); $link.add(`.listtools-watch > a[href="${this.pathname + this.search}"]`) .attr('href', this.pathname + '?' + params) .text(getMsg(newAction)); if (action !== 'watch') return; let require = await mw.loader.using([ 'mediawiki.notification', 'mediawiki.watchstar.widgets' ]); notif = await mw.notify( new (require('mediawiki.watchstar.widgets'))('watch', pn, null, $.noop, { message: getMsg('watched', pn) }).$element, { tag: 'listtools' } ); } catch { notif = await mw.notify(getMsg(action + 'Fail', pn), { tag: 'listtools', type: 'error' }); } finally { $wrapper.html($link); } }; let extGetMain = function () { return this.title; }; let re = new RegExp(`(?:\\?title=|${ mw.util.escapeRegExp(mw.format(mw.config.get('wgArticlePath'), '')) })([^#&?]+)`); let processed = new WeakSet(); let processLinks = ($links, module, titles) => { let isBatch = !!titles; titles = titles || new Set(); $links.each(function (i) { if (processed.has(this)) return; let $link = $links.eq(i); let pn; if (module.useText) { pn = $link.text(); } else { let match = $link.attr('href')?.match(re); if (!match) return; pn = decodeURIComponent(match[1]); } let t = mw.Title.newFromText(pn); if (!t) return; if (module.titlesOnly) { let text = $link.text(); if (text !== pn.replaceAll('_', ' ') && (text !== t.getMainText() || t.namespace === 2) ) { return; } } if ($link.is('.external, .extiw')) { Object.assign(t, { getMain: extGetMain, host: this.host, namespace: 0, title: pn }); } else { if (t.namespace < 0) return; if ($link.hasClass('new')) { t.missing = true; } titles.add(t.getSubjectPage().toText()); } let $tools = $('<span>').addClass('listtools mw-changeslist-links') .data('listtools', t); tools.forEach(tool => { addTool($tools, tool); }); if ($link.is(':is(del, bdi) > :only-child')) { if (module.position === 'end') { $link.parent().parent().append(' ', $tools); } else { $link.parent().after(' ', $tools); } } else if (module.position === 'end') { $link.parent().append(' ', $tools); } else { $link.after(' ', $tools); } if (module.post) { module.post($tools); } processed.add(this); }); if (!isBatch) { getWatched(titles); } }; let tools = [ { name: 'edit', url: t => t.getUrl({ action: 'edit' }) }, { name: 'hist', url: t => !t.missing && t.getUrl({ action: 'history' }) }, { name: 'links', url: t => mw.util.getUrl('Special:WhatLinksHere/' + t) }, { name: 'watch', url: t => !t.host && t.getSubjectPage().getUrl({ action: 'watch' }), callback: watchHandler } ]; let addTool = ($tools, tool, escapedName) => { let t = $tools.data('listtools'); let $duplicate = escapedName && $tools.children('.listtools-' + escapedName); let url = tool.url; if (typeof url === 'function') { url = url(t); if (!url) { $duplicate?.remove(); return; } } let $link = $('<a>').attr('href', url).text(getMsg(tool.name)); if (t.host) { $link.prop('host', t.host); } if (tool.callback) { $link.on('click', tool.callback); } let $wrapper = $('<span>').addClass('listtools-' + tool.name) .append($link); let $next = tool.next && $tools.children('.listtools-' + tool.next); if ($next?.length) { $duplicate?.remove(); $next.before($wrapper); } else if ($duplicate?.length) { $duplicate.replaceWith($wrapper); } else { $tools.append($wrapper); } }; let extend = tool => { if (tool.label && !Object.hasOwn(messages, tool.label)) { messages[tool.name] = tool.label; } if (tool.next) { tool.next = $.escapeSelector(tool.next); } let existingTool = tools.find(t => t.name === tool.name); if (existingTool) { Object.assign(existingTool, tool); } else { tools.push(tool); } let escapedName = existingTool && $.escapeSelector(tool.name); let $allTools = $('.listtools'); $allTools.each(function (i) { addTool($allTools.eq(i), tool, escapedName); }); }; let getWatched = async titles => { if (!Array.isArray(titles)) { titles = [...titles].slice(0, 500); } if (!titles.length) return; (await new mw.Api().post({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages.forEach(page => { if (!page.watched) return; $(`.listtools-watch > a[href="${mw.util.getUrl(page.title, { action: 'watch' })}"]`) .attr('href', mw.util.getUrl(page.title, { action: 'unwatch' })) .text(getMsg('unwatch')); }); getWatched(titles.slice(50)); }; mw.hook('listtools.ready').fire(extend); let catTreeCallback = (records, observer) => { let $links = $(records[0].target).find('.CategoryTreeItem > bdi > a'); if ($links.length) { observer.takeRecords(); observer.disconnect(); processLinks($links, catTreeModule); } }; let catTreeModule = { selector: '.CategoryTreeItem > bdi > a', types: [14, 'CategoryTree'], position: 'end', post: $tools => { $tools.parent().next('.CategoryTreeChildren').each(function () { new MutationObserver(catTreeCallback) .observe(this, { childList: true }); }); } }; let modules = [ { selector: '#mw-pages li > a, #mw-pages li > span > a', types: [14] }, catTreeModule, { selector: '#mw-imagepage-section-linkstoimage a, #mw-imagepage-section-globalusage a', types: [6] }, { selector: '#mw-globalusage-result a', types: ['GlobalUsage'] }, { selector: '.mw-search-result-heading > a, .searchalttitle > a.mw-redirect, .iw-result__title > a, .mw-search-exists a', types: ['Search'] }, { selector: '.mw-search-createlink a', types: ['Search'], titlesOnly: true }, { selector: '#watchlist-edit-form .cdx-table td > label > a', types: ['EditWatchlist'] }, { selector: '.plainlinks > li > a', types: ['AbuseLog'], titlesOnly: true }, { selector: '#mw-allmessagestable td:first-child > a:first-child:not(.new)', types: ['Allmessages'], position: 'end' }, { selector: '.mw-spcontent li a', types: ['DisambiguationPageLinks', 'Listredirects'], titlesOnly: true }, { selector: 'li > a:first-child', types: ['FileDuplicateSearch'] }, { selector: '.TablePager_col_title > a:first-child, .TablePager_col_template > a', types: ['LintErrors'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: 'form > ul > li > a', types: ['Nuke'], position: 'end', titlesOnly: true }, { selector: '.page-assessments a', types: ['PageAssessments'], titlesOnly: true }, { selector: '.TablePager_col_pr_page > a', types: ['Protectedpages'], position: 'end' }, { selector: '#mw-content-text > ul a', types: ['Protectedtitles'], position: 'end' }, { selector: '.mw-fr-pending-changes-page-title', types: ['PendingChanges'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: '#mw-content-text > ul a:first-child', types: ['StablePages'], position: 'end' }, { selector: '.TablePager_col__page a', types: ['TopicSubscriptions'] }, { selector: '.undeleteResult > a', types: ['Undelete'], position: 'end', useText: true }, { selector: '.TablePager_col_img_name > a:first-child', // types: ['Listfiles'], position: 'end' }, { selector: '.mw-newpages-pagename', post: $tools => { let $nodes = $tools.parent().contents(); $nodes.slice( $nodes.index($tools) + 1, $nodes.index($nodes.filter('.mw-newpages-length')) ).replaceWith(' '); } }, { selector: '#mw-whatlinkshere-list li > bdi > a' }, { selector: '.mw-changeslist-log-entry > a:not(.mw-changeslist-log-gblblock a, .mw-changeslist-log-globalauth a)', titlesOnly: true }, { selector: '.mw-logevent-loglines > li:not(.mw-logline-gblblock, .mw-logline-globalauth) > a', types: ['Log'], titlesOnly: true }, { selector: '#mw-diff-otitle1 > strong > a, #mw-diff-ntitle1 > strong > a', types: ['ComparePages'], position: 'end' }, { selector: '#movepage-oldlink, #movepage-newlink', types: ['Movepage'] }, { selector: '.mw-undelete-revision a:not(.mw-userlink, .mw-usertoollinks > a)', types: ['Undelete'], useText: true }, { selector: '.galleryfilename, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a' }, { selector: '.mw-spcontent li a', position: 'end', titlesOnly: true } ]; if (isEdit) { let post = $tools => { if (!$tools[0].closest('.templatesUsed')) return; $tools.parent().contents().last().each(function () { this.textContent = this.textContent.slice(1); }).end().slice(-3, -1).remove(); }; let callback = mw.util.debounce(() => { processLinks( $('.mw-editfooter-list a, #wikiPreview > .previewnote a'), { titlesOnly: true, post } ); }, 500); mw.hook('wikipage.editform').add($form => { callback(); $form.find('.templatesUsed').each(function () { if (processed.has(this)) return; processed.add(this); new MutationObserver(callback) .observe(this, { childList: true, subtree: true }); }); }); } else if (typeof pageType === 'number') { $(() => { processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {}); }); } mw.hook('wikipage.content').add($content => { let titles = new Set(); let $links = $content.find('a'); modules.forEach(module => { if (module.types && !module.types.includes(pageType)) return; processLinks($links.filter(module.selector), module, titles); }); getWatched(titles); }); }()); mw.hook('listtools.ready').add(extend => { // extend({ // name: 'talk', // url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(), // next: 'hist' // }); extend({ name: 'subject', url: t => t.isTalkPage() && t.getSubjectPage().getUrl(), next: 'hist' }); extend({ name: 'last', url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }), next: 'links' }); // extend({ // name: 'purge', // url: t => t.getUrl({ action: 'purge' }), // next: 'watch', // callback: function (e) { // e.preventDefault(); // let $link = $(this); // let $wrapper = $link.parent(); // $link.detach(); // $wrapper.text('purging'); // let pn = $wrapper.closest('.listtools').data('listtools').toText(); // new mw.Api().post({ // action: 'purge', // forcelinkupdate: 1, // titles: pn, // formatversion: 2 // }).then(response => { // if (response.purge[0].purged) { // mw.notify(`Purged "${pn}"'`); // } // }).always(() => { // $wrapper.html($link); // }); // } // }); extend({ name: 'copy', url: '#', callback: function (e) { e.preventDefault(); let text = $(this).closest('.listtools').data('listtools').toText(); let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch (err) {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } } }); }); (mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') && mw.config.get('wgCanonicalSpecialPageName') !== 'GlobalContributions' && (function consecudiff() { mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}'); let isHist = mw.config.get('wgAction') === 'history'; class Consecudiff { constructor(lis, isContribs) { this.isContribs = isContribs; this.isEnhanced = !isHist && !isContribs && lis[0].classList.contains('mw-enhanced-rc'); this.threshold = isContribs ? window.consecudiffContribsThreshold || 120 : isHist ? window.consecudiffHistThreshold || 720 : window.consecudiffThreshold || 720; this.strictMode = !isContribs && !!window.consecudiffDetectInterruptions; this.diffSelector = isHist ? 'a.mw-history-histlinks-previous' : '.mw-changeslist-diff'; this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' || (isHist || isContribs) && 'a.mw-changeslist-date'; this.hybridSelector = this.diffSelector; if (this.permaSelector) { this.hybridSelector += ', ' + this.permaSelector; } this.topClass = isContribs ? 'mw-contributions-current' : 'mw-changeslist-last'; let dependencies = ['mediawiki.util']; if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') { dependencies.push('mediawiki.language.months'); } mw.loader.using(dependencies, () => { let chunks; if (isHist) { chunks = this.chunkByUser(lis); } else { chunks = []; this.groupByTitle(lis).forEach(group => { chunks.push(...this.chunkByUser(group)); }); } let subchunks = []; chunks.forEach(chunk => { subchunks.push(...this.divideByDate(chunk)); }); let linkPairs = []; subchunks.forEach(subchunk => { linkPairs.push(...this.makeLinks(subchunk)); }); linkPairs.forEach(([$span, parent]) => { $span.appendTo(parent); }); }); } groupByTitle(lis) { let selector = this.isContribs ? '.mw-contributions-title' : '.mw-changeslist-title'; let lisByTitle = {}; lis.forEach(li => { let link = (this.isEnhanced ? li.closest('table') : li) .querySelector(selector); if (!link) return; let title = link.textContent; if (!lisByTitle.hasOwnProperty(title)) { lisByTitle[title] = []; } lisByTitle[title].push(li); }); return Object.values(lisByTitle).filter(group => group.length > 1); } chunkByUser(lis) { if (this.isSingleContribs) { return [lis]; } let chunks = [], lastSplitAt = 0, prevUser; this.isSingleContribs = lis.some((li, i) => { let link = li.querySelector('.mw-userlink'); if (!link && this.isContribs) { return true; } let user = link && link.textContent; if (!link || i && user !== prevUser) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevUser = user; }); if (this.isSingleContribs) { return [lis]; } chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } divideByDate(lis) { let chunks = [], lastSplitAt = 0, prevDate; lis.forEach((li, i) => { let date; if (isHist || this.isContribs) { date = this.parseDate( li.querySelector('.mw-changeslist-date').textContent ); } else { date = Date.parse( li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z') ); } if (date) { date = date / 60000; } if (i && prevDate - date > this.threshold) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevDate = date; if (!this.strictMode || lastSplitAt === i) return; let prevDiff = lis[i - 1].querySelector(this.diffSelector); if (prevDiff) { let prevNext = mw.util.getParamValue('oldid', prevDiff.search); if (prevNext !== li.dataset.mwRevid) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } } }); chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } makeLinks(lis) { let count = lis.length; let firstPerma; let start = lis.findIndex(li => ( firstPerma = li.querySelector(this.hybridSelector) )); if (start === -1 || count - start < 2) return []; let end, lastDiff; for (let i = count - 1; i > start; i--) { if (!isHist && !this.isContribs) { lastDiff = lis[i].querySelector(this.diffSelector); if (lastDiff || lis[i].classList.contains('mw-changeslist-src-mw-new') ) { end = i + 1; break; } } if (this.permaSelector && lis[i].querySelector(this.permaSelector)) { end = i + 1; break; } } if (!end) return []; count = end - start; let params = { diff: lis[start].dataset.mwRevid }; if (lastDiff) { params.oldid = mw.util.getParamValue('oldid', lastDiff.search); } else { params.oldid = lis[end - 1].dataset.mwRevid; if (isHist && lis[end - 1].querySelector(this.diffSelector) || this.isContribs && !lis[end - 1].querySelector('.newpage') ) { params.direction = 'prev'; } } let title = !isHist && mw.util.getParamValue('title', firstPerma.search); let url = mw.util.getUrl(title, params); let classes = 'consecudiff'; if (!isHist && lis[start].classList.contains(this.topClass)) { classes += ' consecudiff-top'; } return lis.slice(start, end).map((li, i) => [ $('<span>').addClass(classes).append( $('<a>') .attr('href', url) .text(this.convertNumber(count - i + '/' + count)) ), this.isEnhanced ? li.tagName === 'TR' ? li.lastElementChild : li.querySelector('.mw-changeslist-line-inner') : li ]); } parseDate(s) { let date = Date.parse(s); if (date) { return date; } if (s.includes(',')) date = Date.parse(s.replace(',', '')); if (date) { return date; } if (mw.loader.getState('mediawiki.language.months') !== 'ready') return; s = s.replace(/\D/g, c => { let n = mw.language.convertNumber(c, true); return Number.isNaN(n) ? c : n; }); let h, m; s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => { h = $1; m = $2; return ' '; }); if (!h) return; let y, dateFirst; s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => { y = $2; dateFirst = /\d/.test($1); return $1 + ' '; }); if (!y) return; let mo, d; if (dateFirst) { [d, s] = this.getDate(s); if (!d) return; [mo, s] = this.getMonth(s); if (mo === -1) return; } else { [mo, s] = this.getMonth(s); if (mo === -1) return; [d, s] = this.getDate(s); if (!d) return; } return new Date(y, mo, d, h, m).getTime(); } getMonth(s) { if (!this.months) { this.months = mw.language.months.abbrev .concat(mw.language.months.names, mw.language.months.genitive) .reverse(); } let mo = this.months.findIndex(mn => { let temp = s.replace(mn, ' '); if (temp !== s) { s = temp; return true; } }); if (mo === -1) { let [numeric, temp] = this.getDate(s); numeric = parseInt(numeric); if (numeric > 0 && numeric < 13) { mo = numeric - 1; s = temp; } } else { mo = 11 - mo % 12; } return [mo, s]; } getDate(s) { let d; s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => { d = $2; return $1 + ' '; }); return [d, s]; } convertNumber(num) { try { return mw.language.convertNumber(num); } catch (e) { return num; } } } mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-body').each(function () { let lis = this.querySelectorAll('.mw-contributions-list > li'); if (lis.length > 1) { new Consecudiff([...lis], !isHist); } }); if (isHist) return; let $lists = $content.filter('.mw-changeslist'); if (!$lists.length) { $lists = $content.find('.mw-changeslist'); } $lists.each(function () { let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]'); if (lis.length > 1) { new Consecudiff([...lis]); } }); }); }()); if (mw.config.get('wgNamespaceNumber') === 14 && ( mw.config.get('wgAction') === 'view' || !mw.config.get('wgArticleId') )) { mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.UserInputWidget', 'mediawiki.widgets.datetime', 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement', 'mediawiki.interface.helpers.styles', 'user.options' ]); } $(function moveHistory() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Move history', 't-movehistory').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.moveHistoryDialog) { window.moveHistoryDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter', 'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions', 'mediawiki.interface.helpers.styles' ]); }); }); }); $(function sectionSearch() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Section search', 't-sectionsearch').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.sectionSearchDialog) { window.sectionSearchDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows', 'mediawiki.widgets', 'mediawiki.widgets.NamespacesMultiselectWidget' ]); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth' && mw.loader.using('jquery.tablesorter', function sortCentralAuthByEditCount() { mw.hook('wikipage.content').add($content => { let $table = $content.find('.mw-centralauth-wikislist').has('td'); if (!$table.length) return; $table.tablesorter().data('tablesorter').sort([{ 4: 'desc' }, { 1: 'asc' }]); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && [10, 828].includes(mw.config.get('wgNamespaceNumber')) && !mw.config.get('wgTitle').endsWith('/doc') && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoTestcases.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/TemplatePreviewGuard.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // $(function templatePreviewGuard() { // let button = document.querySelector('input[name="wpTemplateSandboxPreview"]'); // if (!button) return; // let proceed; // button.addEventListener('click', e => { // if (proceed) { // proceed = false; // return; // } // e.preventDefault(); // e.stopPropagation(); // let formData = new FormData(button.form); // let page = formData.get('wpTemplateSandboxPage'); // let temp = formData.get('wpTemplateSandboxTemplate'); // if (!page || !temp) return; // mw.loader.using('mediawiki.api').then(() => ( // new mw.Api().get({ // action: 'query', // titles: page, // prop: 'templates', // tltemplates: temp, // formatversion: 2 // }) // )).always(response => { // if (((((response || {}).query || {}).pages || [])[0] || {}).templates || // confirm(`"${page}" doesn't appear to transclude "${temp}". Continue?`) // ) { // proceed = true; // button.click(); // } // }); // }, true); // if (!mw.config.get('wgArticleId')) return; // let widgetEl = document.querySelector('#wpTemplateSandboxPage.oo-ui-widget'); // if (!widgetEl) return; // let pn = mw.config.get('wgPageName').replace(/_/g, ' '); // mw.loader.using(['mediawiki.api', 'oojs-ui-core']).then(() => ( // new mw.Api().get({ // action: 'query', // titles: pn, // prop: 'transcludedin', // tiprop: 'title', // tilimit: 'max', // formatversion: 2 // }) // )).then(response => { // if (!response.batchcomplete) return; // let pages = response.query.pages[0].transcludedin // .filter(o => o.title !== pn); // if (!pages.length) return; // let widget = OO.ui.infuse(widgetEl); // if (pages.length === 1) { // widget.setValue(pages[0].title); // return; // } // widget.$element.replaceWith( // new OO.ui.ComboBoxInputWidget({ // id: 'wpTemplateSandboxPage', // maxlength: widget.$input.prop('maxLength'), // name: widget.$input.prop('name'), // options: pages // .sort((a, b) => a.ns - b.ns || -(a.title < b.title)) // .map(o => ({ data: o.title })), // placeholder: widget.$input.prop('placeholder'), // tabIndex: widget.getTabIndex(), // value: widget.getValue() // }).on('enter', e => { // e.preventDefault(); // button.click(); // }).$element // ); // }); // }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(async () => { let form = document.getElementById('editform'); if (!form) return; let formData = new FormData(form); let section = formData.get('wpSection'); if (section === 'new') return; let widget = document.getElementById('wpSummaryWidget'); if (!widget) return; let isOld = formData.get('altBaseRevId') > 0 || (formData.get('baseRevId') || formData.get('parentRevId')) !== formData.get('editRevId'); await mw.loader.using([ 'jquery.textSelection', 'mediawiki.util', 'mediawiki.api', 'oojs-ui-core', 'oojs-ui.styles.icons-editing-core' ]); let $textarea = $('#wpTextbox1'); let input = OO.ui.infuse(widget); let button = new OO.ui.ButtonWidget({ framed: false, icon: 'undo', classes: ['autosectionlink-button'], invisibleLabel: true, label: 'Restore previous section link' }).toggle().on('click', () => { let cache = button.getData(); input.setValue(input.getValue().replace( /^(\/\*.*?\*\/)?\s*/, cache[0] ? '/* ' + cache[0] + ' */ ' : '' )); updatePreview(cache[0]); cache.reverse(); }).on('toggle', () => { input.$input.css('width', `calc(100% - ${button.$element.width()}px)`); }); input.$input.after(button.$element); let update = mw.util.debounce($diff => { let lines = $textarea.textSelection('getContents').trimEnd().split('\n'); let firstLineNum, lastLineNum; if (isOld) { let i; $diff.find('td:last-child').each(function () { if (this.classList.contains('diff-lineno')) { i = this.textContent.replace(/\D+/g, '') - 1; } else if (this.classList.contains('diff-context')) { i++; } else if (this.classList.contains('diff-addedline')) { i++; if (!firstLineNum) { firstLineNum = i; } lastLineNum = i; } else if (this.classList.contains('diff-empty')) { if (!firstLineNum) { firstLineNum = i === 0 ? 1 : i; } lastLineNum = i; } }); } else { let origLines = $textarea.prop('defaultValue').trimEnd().split('\n'); firstLineNum = lines.findIndex((line, i) => line !== origLines[i]) + 1; if (!firstLineNum) { firstLineNum = lines.length < origLines.length ? lines.length : 1; } lastLineNum = lines.findLastIndex((line, i) => line !== origLines[i]) + 1; if (!lastLineNum && section) { lastLineNum = 1; } } let modLines = lines.slice(0, lastLineNum || 0); let re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/; let lowest = 7; modLines.slice(firstLineNum).forEach(line => { let match = line.match(re); if (match?.[1].length < lowest) { lowest = match[1].length; } }); let head; modLines.slice(0, firstLineNum).reverse().some(line => { let match = line.match(re); if (match?.[1].length < lowest) { head = match[2]; return true; } }); if (head) { head = head .replace(/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>|<!--.*?-->|\[(?:https?:)?\/\/[^\s\[\]]+\s([^\]]+)\]/gi, '$1$2$3') .replace(/''(.+?)''/g, '$1') .trim(); } else if (modLines.length && lowest === 7 && section < 1 && ( section === '0' || lines.slice(modLines.length).some(line => re.test(line)) )) { head = ''; } let v = input.getValue(); let match = v.match(/^\/\*\s*(.+?)\s*\*\/\s*/); let prev = match?.[1]; if (prev === head) return; input.setValue(( head || head === '' ? '/* ' + head + ' */ ' : '' ) + (match ? v.slice(match[0].length) : v)); button.setData([prev, head]).toggle(true); updatePreview(head); }, 500); let updatePreview = head => { let $comment = $('.mw-summary-preview > .comment'); if (!$comment.length) return; let url; if (head) { url = mw.util.getUrl() + '#' + head.replace(/[\s_]+/g, '_'); } else if (head === '') { head = mw.messages.get('autocomment-top', '(top)'); url = mw.util.getUrl(); } let paren = [...mw.messages.get('parentheses', '($1)')][0]; let $nodes = $comment.contents(); let $ac = $nodes.eq(1); if ($nodes[0]?.textContent === paren && $ac.is('.autocomment:first-child')) { if (head) { $ac.children('a').attr('href', url).children('bdi').text(head); } else { if ($nodes[2]?.nodeType === 3) { $nodes[2].textContent = $nodes[2].textContent.trimStart(); } $ac.remove(); } } else if (head) { let rtl = document.body.classList.contains('sitedir-rtl'); $comment.prepend( paren, $('<span>').addClass('autocomment').append( $('<a>').attr({ href: url, title: mw.config.get('wgPageName').replaceAll('_', ' ') }).text(rtl ? '←' : '→').append( $('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(head) ), mw.messages.get('colon-separator', ': ') ) ); if ($nodes[0]?.nodeType === 3) { let text = $nodes[0].textContent; if (text.startsWith(paren)) { text = text.slice(paren.length); } $nodes[0].textContent = ' ' + text; } } }; if (isOld) { mw.hook('wikipage.diff').add(update); } else { $textarea.on('input', update); mw.hook('ext.CodeMirror.input').add(update); update(); } new mw.Api().loadMessagesIfMissing(['autocomment-top', 'colon-separator', 'parentheses']); mw.loader.addStyleTag('.autosectionlink-button{position:absolute;top:0;right:0;margin:0}'); }); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (function copyRevId() { let handler = function (e) { e.preventDefault(); let text = this.closest('.diff td')?.querySelector('[data-mw-revid]')?.dataset.mwRevid || this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!text) return; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); document.execCommand('copy'); $input.remove(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id') ) ); }); }()); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (() => { let handler = async function (e) { e.preventDefault(); let td = this.closest('.diff td'); let rev = td ? td.querySelector('[data-mw-revid]')?.dataset.mwRevid : this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!rev) { mw.notify(`Couldn't get the revision.`, { tag: 'markasunseen', type: 'error' }); return; } let pn = td ? new URLSearchParams([...td.querySelectorAll('a')].pop()?.search).get('title') : mw.config.get('wgPageName'); if (!pn) return; await mw.loader.using('mediawiki.api'); let result = (await new mw.Api().postWithEditToken({ action: 'setnotificationtimestamp', [td ? 'newerthanrevid' : 'torevid']: rev, titles: pn, formatversion: 2 })).setnotificationtimestamp?.[0]; if (Object.hasOwn(result, 'notificationtimestamp')) { mw.notify(`Marked revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'success' }); } else if (result?.notwatched) { mw.notify('This page is not on your watchlist.', { tag: 'markasunseen', type: 'warn' }); } else { mw.notify(`Couldn't mark revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions after this one as unseen' }).on('click', handler).text('unseen'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions since this one as unseen' }).on('click', handler).text('unseen') ) ); }); })(); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && ((mw.config.get('wgNamespaceNumber') % 2 || mw.config.get('wgNamespaceNumber') === 4) || (mw.config.get('wgWikiID') === 'metawiki' && mw.config.get('wgPageContentModel') === 'wikitext')) && mw.loader.using(['mediawiki.util', 'mediawiki.Title'], function copyUnsig() { let handler = function (e) { e.preventDefault(); let parent = this.closest('li, td'); let ts = parent.textContent.match(/\d\d:\d\d, \d\d? [A-Z][a-z]+ \d{4}/)?.[0]; if (!ts) return; let user = parent.querySelector('.mw-userlink').textContent; if (mw.util.isIPv6Address(user)) { user = user.toUpperCase(); } let temp = mw.util.isIPAddress(user) ? 'unsigned IP' : 'unsigned'; let text = `{{subst:${temp}|${user}|${ts}}}`; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').filter(function () { if (mw.config.get('wgWikiID') === 'metawiki') { return true; } let link = this.querySelector('strong > a') || this.parentElement.querySelector('#differences-prevlink, #differences-nextlink'); if (!link) return; let t = mw.Title.newFromText(mw.util.getParamValue('title', link.search)); return t.isTalkPage() || t.namespace === 4; }).append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig') ) ); }); }); // mw.config.get('wgAction') === 'history' && // mw.loader.using('mediawiki.util', function () { // mw.hook('wikipage.content').add($content => { // $content.find('a.mw-changeslist-date').after(function () { // return [ // ' (', // $('<a>').attr('href', mw.util.getUrl(null, { // action: 'edit', // oldid: this.closest('li').dataset.mwRevid // })).text('e'), // ')' // ]; // }); // }); // }); // ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && // mw.hook('wikipage.content').add($content => { // $content.find('.mw-changeslist-history').parent().after(function () { // return $('<span>').append( // $('<a>').attr( // 'href', // this.firstElementChild.getAttribute('href').slice(0, -7) + 'edit' // ).text('e') // ); // }); // }); if (screen.width < 500) { mw.loader.addStyleTag('@font-face{font-family:CharisW;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/017b2b2ad86e09d3c22b8cf0dfc78247/CharisSILRegular.ttf) format(truetype)} @font-face{font-family:CharisW;font-weight:700;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/6f5069ac6a300dad45383c952e92c573/CharisSILBold.ttf) format(truetype)} body .IPA{font-family:CharisW,sans-serif} .mw-highlight-lines > pre{width:120em}'); location.hash && $(() => { let target = document.querySelector(':target'); if (target?.getBoundingClientRect().top < 0) { target.scrollIntoView(); } }); } ['edit', 'submit'].includes(mw.config.get('wgAction')) && (mw.config.exists('wgCodeEditorCurrentLanguage') || mw.config.exists('cmMode') && mw.config.get('cmMode') !== 'mediawiki') && (function saveNEdit() { let notif; $(document.body).on('click', '#wpSave', async function (e) { if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey || e.originalEvent?.defaultPrevented ) { return; } e.preventDefault(); await mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'jquery.textSelection', 'oojs-ui-core' ]); let button = OO.ui.infuse(this.parentElement).setDisabled(true); let $textarea = $('#wpTextbox1'); let text = $textarea.textSelection('getContents'); let $summary = $('#wpSummary'); let formData = new FormData(this.form); let promise = new mw.Api().postWithEditToken({ action: 'edit', title: mw.config.get('wgPageName'), text: text, section: formData.get('wpSection') || undefined, summary: $summary.textSelection('getContents'), [$('#wpMinoredit').prop('checked') ? 'minor' : 'notminor']: 1, baserevid: formData.get('editRevId'), basetimestamp: formData.get('wpEdittime'), starttimestamp: formData.get('wpStarttime'), watchlist: $('#wpWatchthis').prop('checked') ? 'watch' : 'unwatch', watchlistexpiry: formData.get('wpWatchlistExpiry') || undefined, undo: formData.get('wpUndidRevision') || undefined, undoafter: formData.get('wpUndoAfter') || undefined, contentformat: formData.get('format'), contentmodel: formData.get('model'), assertuser: mw.config.get('wgUserName'), formatversion: 2 }); notif?.close(); notif = null; try { let response = await promise; if (response?.edit?.result !== 'Success') throw ''; $('#editform > input[name="wpUndidRevision"], #editform > input[name="wpUndoAfter"]').remove(); $textarea.data('origtext', text).prop('defaultValue', text); $summary.val($summary.prop('defaultValue')); if (mw.loader.getState('mediawiki.editRecovery.edit') === 'ready') { let storage = mw.loader.moduleRegistry['mediawiki.editRecovery.edit'].packageExports['storage.js']; storage.deleteData(mw.config.get('wgPageName')); storage.closeDatabase(); } notif = await mw.notify(response.edit.nochange ? 'No change' : [ document.createTextNode('Saved'), $('<p>').append( new OO.ui.ButtonWidget({ href: mw.util.getUrl(), target: '_blank', label: 'View' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { diff: response.edit.newrevid || 'cur', diffonly: 1 }), target: '_blank', label: 'Diff' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { action: 'history' }), target: '_blank', label: 'History' }).$element )[0] ], { tag: 'savenedit' }); } catch (error) { notif = await mw.notify(error?.error?.info || error || 'Save failed', { autoHideSeconds: 'long', tag: 'savenedit', type: 'error' }); } finally { button.setDisabled(); } }); }()); mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgAction') === 'view' && mw.config.get('wgCategories')?.some(c => c.endsWith(' actors') || c.endsWith(' actresses')) && $(() => { let n = $.escapeSelector(mw.config.get('wgTitle').replace(/ \(.+\)$/, '')); let $links = $(`.hatnote a[title$="${n} filmography"], .hatnote a[title*="${n} on "], .hatnote a[title*="${n} performances"]`); if (!$links.length) return; let titles = {}; $links = $links.filter(function () { let text = this.textContent; return !(titles[text] = Object.hasOwn(titles, text)); }); mw.notify( $links.length === 1 ? $links.clone() : $('<ul>').append($links.clone().wrap('<li>').parent()), { autoHideSeconds: 'long' } ); }); ['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/RCMuter.js&action=raw&ctype=text/javascript', 's'); location.hostname.endsWith('.wikipedia.org') && mw.config.get('wgNamespaceNumber') % 2 === 0 && // mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.util')).then(function refRenamer() { if (!document.getElementById('p-tb')) return; let messages = Object.assign({ portlet: 'RefRenamer', loading: 'Loading RefRenamer...' }, window.refrenamerMessages); let clicked; mw.util.addPortletLink('p-tb', '#', messages.portlet, 't-refrenamer').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.refRenamer) { window.refRenamer(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript'); mw.notify(messages.loading, { autoHideSeconds: 'long', tag: 'refrenamer' }); }); }); if (['edit', 'submit'].includes(mw.config.get('wgAction'))) { mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/ExpandContractions.js&action=raw&ctype=text/javascript', 's'); mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/Unpipe.js&action=raw&ctype=text/javascript', 's'); } mw.config.get('wgAction') !== 'history' && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopyCodeBlock.js&action=raw&ctype=text/javascript', 's'); mw.config.exists('wgDiffNewId') && mw.config.get('wgDiscussionToolsFeaturesEnabled') && (function () { let data = {}, clickHandler, autoClear, run; window.dtc = data; let highlight = revId => { let ids = data[revId]; if (!ids || !ids.length) return; mw.loader.moduleRegistry['ext.discussionTools.init'].packageExports['highlighter.js'] .highlightNewComments(mw.dt.pageThreads, true, ids); if (clickHandler) { $(document.body).off('click', clickHandler); return; } $._data(document.body, 'events').click.some(o => { if (String(o.handler).includes('highlighter.clearHighlightTargetComment(')) { $(document.body).off('click', o.handler); clickHandler = o.handler; return true; } }); $._data(window, 'events').popstate.some(o => { if (String(o.handler).includes('highlighter.highlightTargetComment(')) { $(window).off('popstate', o.handler); return true; } }); }; let scroll = revId => { let ids = data[revId]; if (!ids || !ids.length) return; let yToSpan = Object.fromEntries( ids.map(id => document.getElementById(id)).filter(Boolean) .map(span => [span.getBoundingClientRect().y, span]) ); let ys = Object.keys(yToSpan); if (!ys.length) return; let lower = ys.filter(y => y > 10); if (!lower.length || Math.max(...lower) < document.documentElement.clientHeight ) { yToSpan[Math.min(...ys)].scrollIntoView(); } else { yToSpan[Math.min(...lower)].scrollIntoView(); } }; let scrollToNext = function (e) { e.preventDefault(); let revId = mw.config.get('wgDiffOldId'); if (!revId || !data[revId]) return; let i = data[revId].indexOf( this.closest('[data-mw-thread-id]').dataset.mwThreadId ); if (i === -1) return; let next = data[revId][i + 1] || data[revId][0]; document.getElementById(next).scrollIntoView(); }; mw.hook('wikipage.content').add(async $content => { let revId = mw.config.get('wgDiffOldId'); if (!revId) return; let param = new URLSearchParams(location.search).get('diffonly'); if (param && param !== '0') return; if (data[revId]) { highlight(revId); return; } await mw.loader.using(['ext.discussionTools.init', 'mediawiki.util']); let begin = Date.parse($('#mw-diff-otitle1 .mw-diff-timestamp').data('timestamp')); data[revId] = mw.dt.pageThreads.getCommentItems() .filter(c => c.timestamp > begin).map(c => c.id); if (!data[revId].length) return; await new Promise(setTimeout); highlight(revId); $content.find('.ext-discussiontools-init-replylink-buttons').filter(function () { return data[revId].includes(this.dataset.mwThreadId); }).children('span:last-of-type').before( ' | ', $('<a>').attr({ href: '#', role: 'button' }).text('next').on('click', scrollToNext) ); if (run || !document.getElementById('p-tb')) return; run = true; let portlet = mw.util.addPortletLink('p-tb', '#', 'Scroll to next', 't-scrolltonext'); portlet.firstElementChild.addEventListener('click', e => { e.preventDefault(); scroll(mw.config.get('wgDiffOldId')); }); mw.util.addPortletLink('p-tb', '#', 'Toggle highlight', 't-togglehighlight').firstElementChild.addEventListener('click', e => { e.preventDefault(); autoClear = !autoClear; if (autoClear) { $(document.body).on('click', clickHandler)[0].click(); } else { highlight(mw.config.get('wgDiffOldId')); } }); mw.loader.addStyleTag(`#t-scrolltonext{position:fixed;bottom:${portlet.clientHeight}px} #t-togglehighlight{position:fixed;bottom:0}`); }); }()); mw.config.get('wgNamespaceNumber') === 6 && mw.config.get('wgAction') === 'view' && mw.hook('wikipage.content').add($content => { $content.find('.filehistory .mw-usertoollinks-contribs').after(function () { return [ ' | ', $('<a>').attr('href', `${ mw.config.get('wgScript') }?title=Special:ListFiles/${ this.pathname.replace(/^.+\//, '') }&ilshowall=1`).text('uploads') ]; }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(function () { if (!$('input[name="wpSection"]').val()) return; mw.hook('wikipage.content').add(async $content => { let $refs = $content.find('.mw-ext-cite-warning-sectionpreview_no_text'); if (!$refs.length) return; let ids = {}; $refs.each(function () { ids[this.closest('[id]').id.replace(/-\d+$/, '')] = this; }); let response = await $.get(`/api/rest_v1/page/html/${encodeURIComponent(mw.config.get('wgPageName'))}`); $($.parseHTML(response)).find('.mw-reference-text').each(function () { ids[this.id.replace(/^mw-reference-text-|-\d+$/g, '')]?.replaceWith(this); }); }); }); mw.hook('moremenu.ready').add(config => { $('#mm-page-purge-cache > a').on('click', e => { e.preventDefault(); new mw.Api().post({ action: 'purge', forcelinkupdate: 1, titles: config.page.name, formatversion: 2 }).then(() => { location.href = mw.util.getUrl(); }); }); $('#mm-page-search-search-history-wikiblame > a').on('click', function (e) { e.preventDefault(); let q = prompt(); if (q === null) return; let href = this.href; if (q) { let removal = q[0] === '!'; if (removal) { q = q.slice(1); } href += '&needle=' + encodeURIComponent(q); if (removal) { href += '&binary_search_inverse=on'; } href += '&force_wikitags=on'; } open(href, '_blank'); }); $('#mm-page-expand-templates > a').on('click auxclick', function (e) { if (e.which > 2) return; e.preventDefault(); let revId = mw.config.get('wgRevisionId') || Number($('input[name=oldid]').val()); let url = revId ? '/w/rest.php/v1/revision/' + revId : '/w/rest.php/v1/page/' + config.page.encodedName; $.get(url).then(response => { $('<form>').attr({ method: 'post', action: this.href, target: '_blank' }).append( [ ['wpInput', response.source], ['wpContextTitle', config.page.name], ['wpRemoveComments', 1] ].map(([n, v]) => $('<input>').attr({ name: n, type: 'hidden' }).val(v)) ).appendTo(document.body).trigger('submit').remove(); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'ApiSandbox' && mw.hook('apisandbox.formatRequest').add((...args) => { args[4].complete = function () { setTimeout(() => { mw.hook('wikipage.content').fire($('.oo-ui-pageLayout-active')); }, 100); }; }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.storage')).then(async () => { let infuseAndCall = (query, method, ...args) => { let $widget = $(query); if ($widget.length) { return OO.ui.infuse($widget)[method](...args); } }; let section = $('input[name="wpSection"]').val(); if (section) { let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let save = () => { let newSource = $textarea.textSelection('getContents'); if (newSource === source) { mw.storage.session.remove('editfullpage'); } else { mw.storage.session.setObject('editfullpage', [ mw.config.get('wgPageName'), section, newSource.trimEnd(), infuseAndCall('#wpSummaryWidget', 'getValue') || '', Number(infuseAndCall('#wpMinoreditWidget', 'isSelected')) || 0, Number(infuseAndCall('#wpWatchthisWidget', 'isSelected')) || 0, infuseAndCall('#wpWatchlistExpiryWidget', 'getValue') || 'infinite' ]); } }; await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); setInterval(() => { mw.requestIdleCallback(save); }, 3000); window.addEventListener('beforeunload', save); return; } let data = mw.storage.session.getObject('editfullpage'); mw.storage.session.remove('editfullpage'); console.log(data); if (!data || data[0] !== mw.config.get('wgPageName')) return; let isNew = data[1] === 'new'; let isLead = data[1] === '0'; let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let newSource, start, msg, notifOpts = { autoHideSeconds: 'long' }; let orig = []; if (isNew) { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); newSource = source + (data[3] ? '\n== ' + data[3] + ' ==\n\n' : '\n') + data[2] + '\n'; start = source.length; } else { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core', 'mediawiki.api']); let { parse } = await new mw.Api().get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'sections', wrapoutputclass: '', disablelimitreport: 1, disableeditsection: 1, disabletoc: 1, formatversion: 2 }); let target = !isLead && parse.sections.find(s => s.index === data[1]); if (isLead || target) { let next = parse.sections.find(s => s.index - 1 === Number(data[1])); newSource = (isLead ? '' : [...source].slice(0, target.byteoffset)).join('') + data[2] + (next ? '\n\n' + [...source].slice(next.byteoffset).join('') : '\n'); start = isLead ? 0 : target.byteoffset; } else { newSource = source + '\n\n' + data[2] + '\n'; start = source.length; msg = `Section restored. Couldn't find the section. The source is appended at bottom.`; notifOpts.type = 'warn'; } orig[0] = infuseAndCall('#wpSummaryWidget', 'getValue'); infuseAndCall('#wpSummaryWidget', 'setValue', data[3]); } $textarea.textSelection('setContents', newSource); orig[1] = infuseAndCall('#wpMinoreditWidget', 'getSelected'); infuseAndCall('#wpMinoreditWidget', 'setSelected', data[4]); orig[2] = infuseAndCall('#wpWatchthisWidget', 'getSelected'); infuseAndCall('#wpWatchthisWidget', 'setSelected', data[5]); orig[3] = infuseAndCall('#wpWatchlistExpiryWidget', 'getValue'); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', data[6]); setTimeout(() => { $textarea.textSelection('setSelection', { start }); }); let notif = await mw.notify($([ document.createTextNode(msg || 'Section restored.'), $('<p>').append( new OO.ui.ButtonWidget({ flags: 'destructive', label: 'Discard' }).on('click', () => { $textarea.textSelection('setContents', source); if (orig[0]) { infuseAndCall('#wpSummaryWidget', 'setValue', orig[0]); } infuseAndCall('#wpMinoreditWidget', 'setSelected', orig[1]); infuseAndCall('#wpWatchthisWidget', 'setSelected', orig[2]); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', orig[3]); notif.close(); }).$element )[0] ]), notifOpts); }); mw.config.exists('wgPostEdit') && mw.loader.using('mediawiki.storage', () => { mw.storage.session.remove('editfullpage'); }); mw.config.get('wgAction') === 'history' && mw.hook('wikipage.content').add(async $content => { if (!$content.has('.mw-history-line-updated').length) return; let href = $content.find('a.mw-history-histlinks-current:not(.mw-history-line-updated a)').attr('href'); if (!href) { await mw.loader.using(['mediawiki.api', 'mediawiki.util']); let page = (await new mw.Api().get({ action: 'query', titles: mw.config.get('wgPageName'), prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev >= page.lastrevid) return; href = mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); } $content.find('.mw-history-compareselectedversions-button').first().after( ' ', $('<a>').attr({ class: 'unseendiff', href: href }).text('unseen') ); }); (async () => { let cspn = mw.config.get('wgCanonicalSpecialPageName'); let isBp = cspn === 'Blankpage'; if (!isBp && cspn !== 'Watchlist') return; await mw.loader.using('mediawiki.util'); let notify = async (text, options, pn) => { let msg = [document.createTextNode(text)]; if (pn) { msg.push( $('<p>').append( $('<a>').attr('href', mw.util.getUrl(pn)).text(pn), ' ', $('<span>').addClass('mw-changeslist-links').append( $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'edit' })) .text('edit') ), $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'history' })) .text('history') ) ) )[0] ); } if (isBp) { await $.ready; $('#mw-content-text').html(msg); } else { return mw.notify(msg, Object.assign(options || {}, { tag: 'unseendiff' })); } }; let getUrl = async pn => { await mw.loader.using('mediawiki.api'); let page = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; if (!page.notificationtimestamp) { notify(`Couldn't get the last seen time.`, { type: 'warn' }, pn); return; } let rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (rev === page.lastrevid) { notify('Already seen.', { type: 'warn' }, pn); return; } if (!rev) { rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, rvdir: 'newer', formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev) { notify(`Couldn't get the last seen revision.`, { type: 'warn' }, pn); return; } } if (rev > page.lastrevid) { notify(`Invalid rev for "${pn}" (rev: ${rev}, lastrevid: ${page.lastrevid})`, { autoHideSeconds: 'long', type: 'warn' }, pn); return; } return mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); }; if (isBp) { let pn = mw.config.get('wgTitle').match(/^[^/]+\/unseendiff\/(.+)$/)?.[1]; if (!pn) return; notify('Loading...', null, pn); let href = await getUrl(pn); if (!href) return; notify('Redirecting...', null, pn); location.href = href; return; } let handler = async function (e) { if (e.which > 2) return; e.preventDefault(); let pn = this.dataset.pn; if (!pn) { notify(`Couldn't get the page name.`, { type: 'error' }); return; } let notifPromise = notify('Loading...', { autoHideSeconds: 'long' }); let href = await getUrl(pn); if (!href) return; $(`.unseendiff-loader[data-pn="${$.escapeSelector(pn)}"]`).attr({ class: 'unseendiff', href: href, target: '_blank' }).off('click auxclick', handler); if (e.type === 'auxclick' || e.ctrlKey || e.metaKey || e.shiftKey) { open(href); } else { this.click(); } (await notifPromise).close(); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-changeslist-src-mw-edit.mw-changeslist-watchedunseen:not(.mw-changeslist-watchedseen) .mw-changeslist-line-inner' ).each(function () { let pn = this.dataset.targetPage || this.closest('[data-target-page]')?.dataset.targetPage || this.closest('table.mw-enhanced-rc')?.querySelector('[data-target-page]')?.dataset.targetPage; if (!pn) return; $('<span>').append( $('<a>').attr({ class: 'unseendiff-loader', href: mw.util.getUrl(`Special:BlankPage/unseendiff/${pn}`), 'data-pn': pn }).on('click auxclick', handler).text('unseen') ).appendTo( [...this.querySelectorAll('.mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); }); })(); ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.using('mediawiki.util', () => { let watched = new Set(); let query = async lis => { let titles = Object.keys(lis).slice(0, 50); if (!titles.length) return; await mw.loader.using('mediawiki.api'); let pages = (await new mw.Api().post({ action: 'query', titles: titles, prop: 'info', inprop: 'notificationtimestamp|watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages; for (let page of pages) { if (!Object.hasOwn(lis, page.title)) continue; if (page.watched) { watched.add(page); $(lis[page.title]).addClass('watched'); } if (!page.notificationtimestamp) continue; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev === page.lastrevid) continue; if (rev > page.lastrevid) { mw.notify($([ document.createTextNode('Invalid rev for "'), $('<a>').attr({ href: mw.util.getUrl(page.title, { action: 'history' }), target: '_blank' }).text(page.title)[0], document.createTextNode(`" (rev: ${rev}, lastrevid: ${page.lastrevid})`), ]), { autoHideSeconds: 'long', type: 'warn' }); continue; } $('<span>').append( $('<a>').attr({ class: 'unseendiff', href: mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }) }).text('unseen') ).appendTo( lis[page.title].map(li => ( [...li.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(li).before(' ') )) ); } titles.forEach(title => { delete lis[title]; }); query(lis); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-contributions-list > li:not(.mw-contributions-current)[data-mw-revid]' ).each(function () { let link = this.querySelector('a.mw-changeslist-date, a.mw-changeslist-history'); let pn = link ? new URLSearchParams(link.search).get('title') : ''; $('<span>').append( $('<a>').attr({ class: 'mw-changeslist-diff', href: mw.util.getUrl(pn, { diff: 'cur', oldid: this.dataset.mwRevid }) }).text('cur') ).appendTo( [...this.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); if (mw.config.get('wgWikiID') === 'wikidatawiki') return; let lis = {}; $content.find('.mw-contributions-title').each(function () { let title = this.textContent; if (!Object.hasOwn(lis, title)) { lis[title] = []; } lis[title].push(this.closest('li')); }); Object.keys(lis).forEach(title => { if (watched.has(title)) { $(lis[title]).addClass('watched'); delete lis[title]; } }); query(lis); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox9.js&action=raw&ctype=text/javascript'); mw.config.get('wgWikiID') === 'metawiki' && (async () => { let css = mw.loader.addStyleTag(`.wishtitle { font-size: 90%; font-style: italic; word-break: break-word; } .wishtitle > a { color: var(--color-warning, #886425); } .wishtitle > a:visited { color: var(--border-color-warning--hover, #735421); } .wishtitle-declined > a { text-decoration: line-through; } .wishtitle-declined > a:hover, .wishtitle-declined > a:focus, .mw-underline-always .wishtitle-declined > a { text-decoration: line-through underline; } #watchlist-edit-form .wishtitle { display: inline-block; } .mw-search-result-heading > .wishtitle, .catchangesviewer-table .wishtitle { display: block; } .catchangesviewer-table:has(.wishtitle) { white-space: wrap; }`); let lang = mw.config.get('wgUserLanguage'); let titles; let loadTitles = async () => { await mw.loader.using('mediawiki.storage'); titles = titles || mw.storage.getObject('wishtitles'); if (titles?.lang !== lang) { titles = { lang, w: [], fa: [] }; } }; let updateTitles = async (crwstatuses, crwcontinue) => { await mw.loader.using('mediawiki.api'); let params = { action: 'query', list: 'communityrequests-wishes', crwlang: lang, crwstatuses: crwstatuses, crwprop: 'title|updated', crwsort: 'updated', crwdir: 'ascending', crwlimit: 'max', crwcontinue: crwcontinue, formatversion: 2 }; if (!crwcontinue && !crwstatuses && titles._) { params.crwcontinue = `|${titles._}|0`; } let response = await new mw.Api().get(params); let wishes = response?.query?.['communityrequests-wishes']; if (wishes?.length) { let $span = $('<span>'); wishes.forEach(w => { let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1]; if (!id) return; titles.w[id - 1] = $span.html(w.title).text(); if (crwstatuses === 'declined') { (titles.wd = titles.wd || []).push(id - 1); } let faId = w.crfatitle?.match(/^Community Wishlist\/FA(\d+)/)?.[1]; if (!faId) return; titles.fa[faId - 1] = w.focusareatitle; }); if (!crwstatuses) { titles._ = wishes.at(-1).updated.replace(/\D/g, ''); } } let expiry = 86400; if (crwstatuses || crwcontinue) { let prev = mw.storage.getObject('_EXPIRY_wishtitles'); if (prev) { expiry = Math.round(Date.now() / 1000) + 86400 - prev; } } mw.storage.setObject('wishtitles', titles, expiry); crwcontinue = response?.continue?.crwcontinue; if (crwcontinue) { await updateTitles(crwstatuses, crwcontinue); } }; let getTitle = id => ( id[0] === 'W' ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1] ); let renderTitle = (title, id, tag = 'span') => { let classes = 'wishtitle'; if (id[0] === 'W' && titles.wd?.includes(id.slice(1) - 1)) { classes += ' wishtitle-declined'; } return $(`<${tag}>`).addClass(classes).append( $('<a>').attr({ href: `/wiki/Community_Wishlist/${id}`, title: `Community Wishlist/${id}` }).text(title) ); }; let callback = ([id, links]) => { let title = getTitle(id); if (!title) { return true; } $(links).after(' ', renderTitle(title, id)); }; let selector = '.mw-changeslist-title, ' + '.mw-changeslist-log-entry > a:not(.mw-userlink), ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' + '#watchlist-edit-form .cdx-table td > label > a, ' + '.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' + '.mw-contributions-title, ' + '#mw-whatlinkshere-list li > bdi > a, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-logevent-loglines > li > a, ' + '#mw-pages li > a, ' + '.catchangesviewer-table td:nth-child(3) > a'; mw.hook('wikipage.content').add(async $content => { let links = {}; $content.find('a').each(function () { if (!this.matches(selector)) return; let id = this.textContent.match( /^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/ )?.[1]; if (!id) return; (links[id] = links[id] || []).push(this); }); links = Object.entries(links); if (!links.length) return; await loadTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles('declined'); links.forEach(callback); }); let pn = mw.config.get('wgRelevantPageName'); let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1]; if (!id) return; await $.ready; let extTitle = document.querySelector('.ext-communityrequests-wish--title'); if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return; await loadTitles(); let title = getTitle(id); if (!title) { await updateTitles(); title = getTitle(id); if (!title) { await updateTitles('declined'); title = getTitle(id); if (!title) return; } } let $title = renderTitle(title, id, 'div'); if (mw.config.get('skin') === 'vector-2022') { $title.prependTo('.vector-page-toolbar'); } else { $title.insertAfter('#firstHeading'); } css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}'; if (extTitle) return; document.title = document.title.replace( pn.replaceAll('_', ' '), `${pn.replace(`Community_Wishlist/${id}`, title)} ($&)` ); })(); mw.config.get('wgWikiID') === 'metawiki' && mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => { if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return; let title = mw.config.get('wgTitle'); if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return; if (isWatched) { await new mw.Api().watch(title + '/Votes', expiry); mw.notify('Watching /Votes too.'); } else { await new mw.Api().unwatch(title + '/Votes'); mw.notify('Unwatched /Votes too.'); } }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && $(async () => { let $input = $('#wpTemplateSandboxTemplate'); if (!$input.length) return; mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}'); let makeTemplateField = () => new OO.ui.FieldLayout( new mw.widgets.TitleInputWidget({ inputId: 'wpTemplateSandboxTemplate', name: 'wpTemplateSandboxTemplate', showMissing: false, value: $input.val() }), { label: 'Template name:' } ); if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') { await mw.loader.using('mediawiki.widgets'); $input.parent().replaceWith(makeTemplateField().$element); return; } let require = await mw.loader.using([ 'ext.TemplateSandbox.TemplateSandboxTitleWidget', 'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options' ]); let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({ $overlay: true, id: 'wpTemplateSandboxPage', maxLength: 255, name: 'wpTemplateSandboxPage', placeholder: 'Page title', required: false, tabIndex: 10, templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val() }); widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}') .data('oouiInfused', widget); let fieldset = new OO.ui.FieldsetLayout({ classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'], id: 'templatesandbox-editform', items: [ makeTemplateField(), new OO.ui.ActionFieldLayout( widget, new OO.ui.ButtonInputWidget({ id: 'wpTemplateSandboxPreview', name: 'wpTemplateSandboxPreview', label: 'Show preview', tabIndex: 10, type: 'submit', useInputTag: true }), { align: 'top' } ) ], label: 'Preview page with this template' }); fieldset.$label.append('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await $.get( `https://en.wikipedia.org/w/rest.php/v1/page/${encodeURIComponent(xfd.page)}/html` ))); await queryTitles(xfd, xfd.titleExtractor($page)); }; let queryCat = async (xfd, gcmcontinue) => { let response = await new mw.Api().get({ action: 'query', prop: 'info|categories', inprop: 'watched', clprop: 'sortkey', clcategories: `Category:${xfd.cat}`, generator: 'categorymembers', gcmtitle: `Category:${xfd.cat}`, gcmlimit: 'max', gcmsort: 'timestamp', gcmdir: 'older', gcmcontinue: gcmcontinue, formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched && p.categories?.[0]?.sortkeyprefix !== ' ') { xfd.pages.push(p.title); } }); if (response?.continue?.gcmcontinue) { await queryCat(xfd, response.continue.gcmcontinue); } }; let show = async (xfd, lastId, isCache) => { if (xfd.portlet && isCache) return; let portletId = 'p-xfdnotifier-' + xfd.id; if (xfd.portlet) { $(xfd.portlet).find('ul').empty(); if (!xfd.pages.length) return; } else { await $.ready; xfd.portlet = mw.util.addPortlet(portletId, xfd.label, '#' + lastId); } let $label = $(`#${portletId}-label`).attr('title', xfd.full); if (xfd.page) { $label.wrapInner($('<a>').attr('href', mw.util.getUrl(xfd.page))); } xfd.pages.forEach(p => { let t = mw.Title.newFromText(p); let isTalk = t.isTalkPage(); let $other = $('<a>').attr({ href: t[isTalk ? 'getSubjectPage' : 'getTalkPage']().getUrl(), title: isTalk ? 'subject' : 'talk' }).text(isTalk ? 's' : 't'); let link = mw.util.addPortletLink(portletId, t.getUrl(), p).querySelector('a'); $('<span>').addClass('xfdnotifier-sublinks').append( $('<span>').append($other), $('<span>').append( $('<a>').attr({ href: t.getUrl({ action: 'history' }), title: 'history' }).text('h') ) ).insertAfter(link); }); }; mw.hook('wikipage.content').add(mw.util.throttle(async () => { let cache = mw.storage.getObject('xfdnotifier') || {}; let lastId = 'p-tb'; for (let xfd of xfds) { let portletId = 'p-xfdnotifier-' + xfd.id; let now = Math.floor(Date.now() / 1000); if (now - cache[xfd.id]?.[0] < 600) { xfd.pages = cache[xfd.id].slice(1); await show(xfd, lastId, true); lastId = portletId; continue; } xfd.pages = []; if (xfd.cat) { await queryCat(xfd); } else if (xfd.page) { await queryPage(xfd); } cache[xfd.id] = [now, ...xfd.pages]; mw.storage.setObject('xfdnotifier', cache, 604800); await show(xfd, lastId); lastId = portletId; } }, 1800000)); })(); 7bgvfu964vhsms13vimi25z0zfnpki5 739835 739832 2026-04-30T06:55:00Z Nardog 40946 739835 javascript text/javascript (async function listTools() { let pageAction = mw.config.get('wgAction'); let isView = pageAction === 'view'; let isEdit = ['edit', 'submit'].includes(pageAction); if (!isView && !isEdit) return; let pageType = mw.config.get('wgCanonicalSpecialPageName') || mw.config.get('wgNamespaceNumber'); if (isView && !pageType && !mw.config.exists('wgRedirectedFrom') && !mw.config.get('wgIsRedirect') && !mw.config.get('wgPageName').includes('/') ) { return; } await mw.loader.using([ 'mediawiki.util', 'mediawiki.Title', 'mediawiki.api', 'mediawiki.interface.helpers.styles' ]); mw.loader.addStyleTag(`.listtools:not(#mw-content-subtitle .listtools) { font-size: 85%; } .listtools, .listtools a { font-weight: normal !important; font-style: normal; } .mw-datatable .listtools { display: block; } .listtools + .mw-whatlinkshere-tools, #watchlist-edit-form .listtools ~ .mw-changeslist-links, .mw-special-DisambiguationPageLinks .listtools + a { display: none; }`); let messages = Object.assign({ watched: 'Added "$1" to your watchlist', watchFail: `Couldn't watch "$1"`, unwatchFail: `Couldn't unwatch "$1"` }, window.listtoolsMessages); let getMsg = (key, ...args) => ( Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key ); let notif; let watchHandler = async function (e) { e.preventDefault(); let $link = $(this); let $wrapper = $link.parent(); $link.detach(); let params = new URLSearchParams(this.search); let action = params.get('action'); $wrapper.text(getMsg(action + 'ing')); let pn = params.get('title').replaceAll('_', ' '); let promise = new mw.Api()[action](pn); if (notif) { notif.close(); notif = null; } try { let result = await promise; if (!result || !result[action + 'ed']) throw ''; let newAction = action === 'watch' ? 'unwatch' : 'watch'; params.set('action', newAction); $link.add(`.listtools-watch > a[href="${this.pathname + this.search}"]`) .attr('href', this.pathname + '?' + params) .text(getMsg(newAction)); if (action !== 'watch') return; let require = await mw.loader.using([ 'mediawiki.notification', 'mediawiki.watchstar.widgets' ]); notif = await mw.notify( new (require('mediawiki.watchstar.widgets'))('watch', pn, null, $.noop, { message: getMsg('watched', pn) }).$element, { tag: 'listtools' } ); } catch { notif = await mw.notify(getMsg(action + 'Fail', pn), { tag: 'listtools', type: 'error' }); } finally { $wrapper.html($link); } }; let extGetMain = function () { return this.title; }; let re = new RegExp(`(?:\\?title=|${ mw.util.escapeRegExp(mw.format(mw.config.get('wgArticlePath'), '')) })([^#&?]+)`); let processed = new WeakSet(); let processLinks = ($links, module, titles) => { let isBatch = !!titles; titles = titles || new Set(); $links.each(function (i) { if (processed.has(this)) return; let $link = $links.eq(i); let pn; if (module.useText) { pn = $link.text(); } else { let match = $link.attr('href')?.match(re); if (!match) return; pn = decodeURIComponent(match[1]); } let t = mw.Title.newFromText(pn); if (!t) return; if (module.titlesOnly) { let text = $link.text(); if (text !== pn.replaceAll('_', ' ') && (text !== t.getMainText() || t.namespace === 2) ) { return; } } if ($link.is('.external, .extiw')) { Object.assign(t, { getMain: extGetMain, host: this.host, namespace: 0, title: pn }); } else { if (t.namespace < 0) return; if ($link.hasClass('new')) { t.missing = true; } titles.add(t.getSubjectPage().toText()); } let $tools = $('<span>').addClass('listtools mw-changeslist-links') .data('listtools', t); tools.forEach(tool => { addTool($tools, tool); }); if ($link.is(':is(del, bdi) > :only-child')) { if (module.position === 'end') { $link.parent().parent().append(' ', $tools); } else { $link.parent().after(' ', $tools); } } else if (module.position === 'end') { $link.parent().append(' ', $tools); } else { $link.after(' ', $tools); } if (module.post) { module.post($tools); } processed.add(this); }); if (!isBatch) { getWatched(titles); } }; let tools = [ { name: 'edit', url: t => t.getUrl({ action: 'edit' }) }, { name: 'hist', url: t => !t.missing && t.getUrl({ action: 'history' }) }, { name: 'links', url: t => mw.util.getUrl('Special:WhatLinksHere/' + t) }, { name: 'watch', url: t => !t.host && t.getSubjectPage().getUrl({ action: 'watch' }), callback: watchHandler } ]; let addTool = ($tools, tool, escapedName) => { let t = $tools.data('listtools'); let $duplicate = escapedName && $tools.children('.listtools-' + escapedName); let url = tool.url; if (typeof url === 'function') { url = url(t); if (!url) { $duplicate?.remove(); return; } } let $link = $('<a>').attr('href', url).text(getMsg(tool.name)); if (t.host) { $link.prop('host', t.host); } if (tool.callback) { $link.on('click', tool.callback); } let $wrapper = $('<span>').addClass('listtools-' + tool.name) .append($link); let $next = tool.next && $tools.children('.listtools-' + tool.next); if ($next?.length) { $duplicate?.remove(); $next.before($wrapper); } else if ($duplicate?.length) { $duplicate.replaceWith($wrapper); } else { $tools.append($wrapper); } }; let extend = tool => { if (tool.label && !Object.hasOwn(messages, tool.label)) { messages[tool.name] = tool.label; } if (tool.next) { tool.next = $.escapeSelector(tool.next); } let existingTool = tools.find(t => t.name === tool.name); if (existingTool) { Object.assign(existingTool, tool); } else { tools.push(tool); } let escapedName = existingTool && $.escapeSelector(tool.name); let $allTools = $('.listtools'); $allTools.each(function (i) { addTool($allTools.eq(i), tool, escapedName); }); }; let getWatched = async titles => { if (!Array.isArray(titles)) { titles = [...titles].slice(0, 500); } if (!titles.length) return; (await new mw.Api().post({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages.forEach(page => { if (!page.watched) return; $(`.listtools-watch > a[href="${mw.util.getUrl(page.title, { action: 'watch' })}"]`) .attr('href', mw.util.getUrl(page.title, { action: 'unwatch' })) .text(getMsg('unwatch')); }); getWatched(titles.slice(50)); }; mw.hook('listtools.ready').fire(extend); let catTreeCallback = (records, observer) => { let $links = $(records[0].target).find('.CategoryTreeItem > bdi > a'); if ($links.length) { observer.takeRecords(); observer.disconnect(); processLinks($links, catTreeModule); } }; let catTreeModule = { selector: '.CategoryTreeItem > bdi > a', types: [14, 'CategoryTree'], position: 'end', post: $tools => { $tools.parent().next('.CategoryTreeChildren').each(function () { new MutationObserver(catTreeCallback) .observe(this, { childList: true }); }); } }; let modules = [ { selector: '#mw-pages li > a, #mw-pages li > span > a', types: [14] }, catTreeModule, { selector: '#mw-imagepage-section-linkstoimage a, #mw-imagepage-section-globalusage a', types: [6] }, { selector: '#mw-globalusage-result a', types: ['GlobalUsage'] }, { selector: '.mw-search-result-heading > a, .searchalttitle > a.mw-redirect, .iw-result__title > a, .mw-search-exists a', types: ['Search'] }, { selector: '.mw-search-createlink a', types: ['Search'], titlesOnly: true }, { selector: '#watchlist-edit-form .cdx-table td > label > a', types: ['EditWatchlist'] }, { selector: '.plainlinks > li > a', types: ['AbuseLog'], titlesOnly: true }, { selector: '#mw-allmessagestable td:first-child > a:first-child:not(.new)', types: ['Allmessages'], position: 'end' }, { selector: '.mw-spcontent li a', types: ['DisambiguationPageLinks', 'Listredirects'], titlesOnly: true }, { selector: 'li > a:first-child', types: ['FileDuplicateSearch'] }, { selector: '.TablePager_col_title > a:first-child, .TablePager_col_template > a', types: ['LintErrors'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: 'form > ul > li > a', types: ['Nuke'], position: 'end', titlesOnly: true }, { selector: '.page-assessments a', types: ['PageAssessments'], titlesOnly: true }, { selector: '.TablePager_col_pr_page > a', types: ['Protectedpages'], position: 'end' }, { selector: '#mw-content-text > ul a', types: ['Protectedtitles'], position: 'end' }, { selector: '.mw-fr-pending-changes-page-title', types: ['PendingChanges'], post: $tools => { $tools.parent().contents().slice(3).remove(); } }, { selector: '#mw-content-text > ul a:first-child', types: ['StablePages'], position: 'end' }, { selector: '.TablePager_col__page a', types: ['TopicSubscriptions'] }, { selector: '.undeleteResult > a', types: ['Undelete'], position: 'end', useText: true }, { selector: '.TablePager_col_img_name > a:first-child', // types: ['Listfiles'], position: 'end' }, { selector: '.mw-newpages-pagename', post: $tools => { let $nodes = $tools.parent().contents(); $nodes.slice( $nodes.index($tools) + 1, $nodes.index($nodes.filter('.mw-newpages-length')) ).replaceWith(' '); } }, { selector: '#mw-whatlinkshere-list li > bdi > a' }, { selector: '.mw-changeslist-log-entry > a:not(.mw-changeslist-log-gblblock a, .mw-changeslist-log-globalauth a)', titlesOnly: true }, { selector: '.mw-logevent-loglines > li:not(.mw-logline-gblblock, .mw-logline-globalauth) > a', types: ['Log'], titlesOnly: true }, { selector: '#mw-diff-otitle1 > strong > a, #mw-diff-ntitle1 > strong > a', types: ['ComparePages'], position: 'end' }, { selector: '#movepage-oldlink, #movepage-newlink', types: ['Movepage'] }, { selector: '.mw-undelete-revision a:not(.mw-userlink, .mw-usertoollinks > a)', types: ['Undelete'], useText: true }, { selector: '.galleryfilename, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a' }, { selector: '.mw-spcontent li a', position: 'end', titlesOnly: true } ]; if (isEdit) { let post = $tools => { if (!$tools[0].closest('.templatesUsed')) return; $tools.parent().contents().last().each(function () { this.textContent = this.textContent.slice(1); }).end().slice(-3, -1).remove(); }; let callback = mw.util.debounce(() => { processLinks( $('.mw-editfooter-list a, #wikiPreview > .previewnote a'), { titlesOnly: true, post } ); }, 500); mw.hook('wikipage.editform').add($form => { callback(); $form.find('.templatesUsed').each(function () { if (processed.has(this)) return; processed.add(this); new MutationObserver(callback) .observe(this, { childList: true, subtree: true }); }); }); } else if (typeof pageType === 'number') { $(() => { processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {}); }); } mw.hook('wikipage.content').add($content => { let titles = new Set(); let $links = $content.find('a'); modules.forEach(module => { if (module.types && !module.types.includes(pageType)) return; processLinks($links.filter(module.selector), module, titles); }); getWatched(titles); }); }()); mw.hook('listtools.ready').add(extend => { // extend({ // name: 'talk', // url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(), // next: 'hist' // }); extend({ name: 'subject', url: t => t.isTalkPage() && t.getSubjectPage().getUrl(), next: 'hist' }); extend({ name: 'last', url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }), next: 'links' }); // extend({ // name: 'purge', // url: t => t.getUrl({ action: 'purge' }), // next: 'watch', // callback: function (e) { // e.preventDefault(); // let $link = $(this); // let $wrapper = $link.parent(); // $link.detach(); // $wrapper.text('purging'); // let pn = $wrapper.closest('.listtools').data('listtools').toText(); // new mw.Api().post({ // action: 'purge', // forcelinkupdate: 1, // titles: pn, // formatversion: 2 // }).then(response => { // if (response.purge[0].purged) { // mw.notify(`Purged "${pn}"'`); // } // }).always(() => { // $wrapper.html($link); // }); // } // }); extend({ name: 'copy', url: '#', callback: function (e) { e.preventDefault(); let text = $(this).closest('.listtools').data('listtools').toText(); let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch (err) {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } } }); }); (mw.config.get('wgNamespaceNumber') || mw.config.get('wgAction') !== 'view') && mw.config.get('wgCanonicalSpecialPageName') !== 'GlobalContributions' && (function consecudiff() { mw.loader.addStyleTag('.consecudiff::before{content:" ["} .consecudiff::after{content:"]"} .consecudiff-top::before{content:" ⟨"} .consecudiff-top::after{content:"⟩"}'); let isHist = mw.config.get('wgAction') === 'history'; class Consecudiff { constructor(lis, isContribs) { this.isContribs = isContribs; this.isEnhanced = !isHist && !isContribs && lis[0].classList.contains('mw-enhanced-rc'); this.threshold = isContribs ? window.consecudiffContribsThreshold || 120 : isHist ? window.consecudiffHistThreshold || 720 : window.consecudiffThreshold || 720; this.strictMode = !isContribs && !!window.consecudiffDetectInterruptions; this.diffSelector = isHist ? 'a.mw-history-histlinks-previous' : '.mw-changeslist-diff'; this.permaSelector = this.isEnhanced && '.mw-enhanced-rc-time > a' || (isHist || isContribs) && 'a.mw-changeslist-date'; this.hybridSelector = this.diffSelector; if (this.permaSelector) { this.hybridSelector += ', ' + this.permaSelector; } this.topClass = isContribs ? 'mw-contributions-current' : 'mw-changeslist-last'; let dependencies = ['mediawiki.util']; if ((isHist || isContribs) && mw.config.get('wgUserLanguage') !== 'en') { dependencies.push('mediawiki.language.months'); } mw.loader.using(dependencies, () => { let chunks; if (isHist) { chunks = this.chunkByUser(lis); } else { chunks = []; this.groupByTitle(lis).forEach(group => { chunks.push(...this.chunkByUser(group)); }); } let subchunks = []; chunks.forEach(chunk => { subchunks.push(...this.divideByDate(chunk)); }); let linkPairs = []; subchunks.forEach(subchunk => { linkPairs.push(...this.makeLinks(subchunk)); }); linkPairs.forEach(([$span, parent]) => { $span.appendTo(parent); }); }); } groupByTitle(lis) { let selector = this.isContribs ? '.mw-contributions-title' : '.mw-changeslist-title'; let lisByTitle = {}; lis.forEach(li => { let link = (this.isEnhanced ? li.closest('table') : li) .querySelector(selector); if (!link) return; let title = link.textContent; if (!lisByTitle.hasOwnProperty(title)) { lisByTitle[title] = []; } lisByTitle[title].push(li); }); return Object.values(lisByTitle).filter(group => group.length > 1); } chunkByUser(lis) { if (this.isSingleContribs) { return [lis]; } let chunks = [], lastSplitAt = 0, prevUser; this.isSingleContribs = lis.some((li, i) => { let link = li.querySelector('.mw-userlink'); if (!link && this.isContribs) { return true; } let user = link && link.textContent; if (!link || i && user !== prevUser) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevUser = user; }); if (this.isSingleContribs) { return [lis]; } chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } divideByDate(lis) { let chunks = [], lastSplitAt = 0, prevDate; lis.forEach((li, i) => { let date; if (isHist || this.isContribs) { date = this.parseDate( li.querySelector('.mw-changeslist-date').textContent ); } else { date = Date.parse( li.dataset.mwTs.replace(/(....)(..)(..)(..)(..)(..)/, '$1-$2-$3T$4:$5:$6Z') ); } if (date) { date = date / 60000; } if (i && prevDate - date > this.threshold) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } prevDate = date; if (!this.strictMode || lastSplitAt === i) return; let prevDiff = lis[i - 1].querySelector(this.diffSelector); if (prevDiff) { let prevNext = mw.util.getParamValue('oldid', prevDiff.search); if (prevNext !== li.dataset.mwRevid) { chunks.push(lis.slice(lastSplitAt, i)); lastSplitAt = i; } } }); chunks.push(lis.slice(lastSplitAt)); return chunks.filter(chunk => chunk.length > 1); } makeLinks(lis) { let count = lis.length; let firstPerma; let start = lis.findIndex(li => ( firstPerma = li.querySelector(this.hybridSelector) )); if (start === -1 || count - start < 2) return []; let end, lastDiff; for (let i = count - 1; i > start; i--) { if (!isHist && !this.isContribs) { lastDiff = lis[i].querySelector(this.diffSelector); if (lastDiff || lis[i].classList.contains('mw-changeslist-src-mw-new') ) { end = i + 1; break; } } if (this.permaSelector && lis[i].querySelector(this.permaSelector)) { end = i + 1; break; } } if (!end) return []; count = end - start; let params = { diff: lis[start].dataset.mwRevid }; if (lastDiff) { params.oldid = mw.util.getParamValue('oldid', lastDiff.search); } else { params.oldid = lis[end - 1].dataset.mwRevid; if (isHist && lis[end - 1].querySelector(this.diffSelector) || this.isContribs && !lis[end - 1].querySelector('.newpage') ) { params.direction = 'prev'; } } let title = !isHist && mw.util.getParamValue('title', firstPerma.search); let url = mw.util.getUrl(title, params); let classes = 'consecudiff'; if (!isHist && lis[start].classList.contains(this.topClass)) { classes += ' consecudiff-top'; } return lis.slice(start, end).map((li, i) => [ $('<span>').addClass(classes).append( $('<a>') .attr('href', url) .text(this.convertNumber(count - i + '/' + count)) ), this.isEnhanced ? li.tagName === 'TR' ? li.lastElementChild : li.querySelector('.mw-changeslist-line-inner') : li ]); } parseDate(s) { let date = Date.parse(s); if (date) { return date; } if (s.includes(',')) date = Date.parse(s.replace(',', '')); if (date) { return date; } if (mw.loader.getState('mediawiki.language.months') !== 'ready') return; s = s.replace(/\D/g, c => { let n = mw.language.convertNumber(c, true); return Number.isNaN(n) ? c : n; }); let h, m; s = s.replace(/(\d\d?)[.:h](\d\d?)/, ($0, $1, $2) => { h = $1; m = $2; return ' '; }); if (!h) return; let y, dateFirst; s = s.replace(/^(.*?)(\d{4})(?!\d)/, ($0, $1, $2) => { y = $2; dateFirst = /\d/.test($1); return $1 + ' '; }); if (!y) return; let mo, d; if (dateFirst) { [d, s] = this.getDate(s); if (!d) return; [mo, s] = this.getMonth(s); if (mo === -1) return; } else { [mo, s] = this.getMonth(s); if (mo === -1) return; [d, s] = this.getDate(s); if (!d) return; } return new Date(y, mo, d, h, m).getTime(); } getMonth(s) { if (!this.months) { this.months = mw.language.months.abbrev .concat(mw.language.months.names, mw.language.months.genitive) .reverse(); } let mo = this.months.findIndex(mn => { let temp = s.replace(mn, ' '); if (temp !== s) { s = temp; return true; } }); if (mo === -1) { let [numeric, temp] = this.getDate(s); numeric = parseInt(numeric); if (numeric > 0 && numeric < 13) { mo = numeric - 1; s = temp; } } else { mo = 11 - mo % 12; } return [mo, s]; } getDate(s) { let d; s = s.replace(/(^|\D)(\d\d?)(?!\d)/, ($0, $1, $2) => { d = $2; return $1 + ' '; }); return [d, s]; } convertNumber(num) { try { return mw.language.convertNumber(num); } catch (e) { return num; } } } mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-body').each(function () { let lis = this.querySelectorAll('.mw-contributions-list > li'); if (lis.length > 1) { new Consecudiff([...lis], !isHist); } }); if (isHist) return; let $lists = $content.filter('.mw-changeslist'); if (!$lists.length) { $lists = $content.find('.mw-changeslist'); } $lists.each(function () { let lis = this.querySelectorAll('.mw-changeslist-edit:not(.mw-changeslist-src-mw-categorize)[data-mw-revid]'); if (lis.length > 1) { new Consecudiff([...lis]); } }); }); }()); if (mw.config.get('wgNamespaceNumber') === 14 && ( mw.config.get('wgAction') === 'view' || !mw.config.get('wgArticleId') )) { mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.UserInputWidget', 'mediawiki.widgets.datetime', 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-movement', 'mediawiki.interface.helpers.styles', 'user.options' ]); } $(function moveHistory() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Move history', 't-movehistory').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.moveHistoryDialog) { window.moveHistoryDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter', 'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions', 'mediawiki.interface.helpers.styles' ]); }); }); }); $(function sectionSearch() { if (!document.getElementById('p-tb')) return; mw.loader.using('mediawiki.util', () => { let clicked; mw.util.addPortletLink('p-tb', '#', 'Section search', 't-sectionsearch').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.sectionSearchDialog) { window.sectionSearchDialog.open(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript'); mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows', 'mediawiki.widgets', 'mediawiki.widgets.NamespacesMultiselectWidget' ]); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth' && mw.loader.using('jquery.tablesorter', function sortCentralAuthByEditCount() { mw.hook('wikipage.content').add($content => { let $table = $content.find('.mw-centralauth-wikislist').has('td'); if (!$table.length) return; $table.tablesorter().data('tablesorter').sort([{ 4: 'desc' }, { 1: 'asc' }]); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && [10, 828].includes(mw.config.get('wgNamespaceNumber')) && !mw.config.get('wgTitle').endsWith('/doc') && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/AutoTestcases.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/TemplatePreviewGuard.js&action=raw&ctype=text/javascript', 's'); // ['edit', 'submit'].includes(mw.config.get('wgAction')) && // $(function templatePreviewGuard() { // let button = document.querySelector('input[name="wpTemplateSandboxPreview"]'); // if (!button) return; // let proceed; // button.addEventListener('click', e => { // if (proceed) { // proceed = false; // return; // } // e.preventDefault(); // e.stopPropagation(); // let formData = new FormData(button.form); // let page = formData.get('wpTemplateSandboxPage'); // let temp = formData.get('wpTemplateSandboxTemplate'); // if (!page || !temp) return; // mw.loader.using('mediawiki.api').then(() => ( // new mw.Api().get({ // action: 'query', // titles: page, // prop: 'templates', // tltemplates: temp, // formatversion: 2 // }) // )).always(response => { // if (((((response || {}).query || {}).pages || [])[0] || {}).templates || // confirm(`"${page}" doesn't appear to transclude "${temp}". Continue?`) // ) { // proceed = true; // button.click(); // } // }); // }, true); // if (!mw.config.get('wgArticleId')) return; // let widgetEl = document.querySelector('#wpTemplateSandboxPage.oo-ui-widget'); // if (!widgetEl) return; // let pn = mw.config.get('wgPageName').replace(/_/g, ' '); // mw.loader.using(['mediawiki.api', 'oojs-ui-core']).then(() => ( // new mw.Api().get({ // action: 'query', // titles: pn, // prop: 'transcludedin', // tiprop: 'title', // tilimit: 'max', // formatversion: 2 // }) // )).then(response => { // if (!response.batchcomplete) return; // let pages = response.query.pages[0].transcludedin // .filter(o => o.title !== pn); // if (!pages.length) return; // let widget = OO.ui.infuse(widgetEl); // if (pages.length === 1) { // widget.setValue(pages[0].title); // return; // } // widget.$element.replaceWith( // new OO.ui.ComboBoxInputWidget({ // id: 'wpTemplateSandboxPage', // maxlength: widget.$input.prop('maxLength'), // name: widget.$input.prop('name'), // options: pages // .sort((a, b) => a.ns - b.ns || -(a.title < b.title)) // .map(o => ({ data: o.title })), // placeholder: widget.$input.prop('placeholder'), // tabIndex: widget.getTabIndex(), // value: widget.getValue() // }).on('enter', e => { // e.preventDefault(); // button.click(); // }).$element // ); // }); // }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(async () => { let form = document.getElementById('editform'); if (!form) return; let formData = new FormData(form); let section = formData.get('wpSection'); if (section === 'new') return; let widget = document.getElementById('wpSummaryWidget'); if (!widget) return; let isOld = formData.get('altBaseRevId') > 0 || (formData.get('baseRevId') || formData.get('parentRevId')) !== formData.get('editRevId'); await mw.loader.using([ 'jquery.textSelection', 'mediawiki.util', 'mediawiki.api', 'oojs-ui-core', 'oojs-ui.styles.icons-editing-core' ]); let $textarea = $('#wpTextbox1'); let input = OO.ui.infuse(widget); let button = new OO.ui.ButtonWidget({ framed: false, icon: 'undo', classes: ['autosectionlink-button'], invisibleLabel: true, label: 'Restore previous section link' }).toggle().on('click', () => { let cache = button.getData(); input.setValue(input.getValue().replace( /^(\/\*.*?\*\/)?\s*/, cache[0] ? '/* ' + cache[0] + ' */ ' : '' )); updatePreview(cache[0]); cache.reverse(); }).on('toggle', () => { input.$input.css('width', `calc(100% - ${button.$element.width()}px)`); }); input.$input.after(button.$element); let update = mw.util.debounce($diff => { let lines = $textarea.textSelection('getContents').trimEnd().split('\n'); let firstLineNum, lastLineNum; if (isOld) { let i; $diff.find('td:last-child').each(function () { if (this.classList.contains('diff-lineno')) { i = this.textContent.replace(/\D+/g, '') - 1; } else if (this.classList.contains('diff-context')) { i++; } else if (this.classList.contains('diff-addedline')) { i++; if (!firstLineNum) { firstLineNum = i; } lastLineNum = i; } else if (this.classList.contains('diff-empty')) { if (!firstLineNum) { firstLineNum = i === 0 ? 1 : i; } lastLineNum = i; } }); } else { let origLines = $textarea.prop('defaultValue').trimEnd().split('\n'); firstLineNum = lines.findIndex((line, i) => line !== origLines[i]) + 1; if (!firstLineNum) { firstLineNum = lines.length < origLines.length ? lines.length : 1; } lastLineNum = lines.length; for (let i = 1, x = lastLineNum, y = origLines.length; (section ? i < x : i <= x) && lines[x - i] === origLines[y - i]; i++ ) { lastLineNum--; } } let modLines = lines.slice(0, lastLineNum || 0); let re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/; let lowest = 7; modLines.slice(firstLineNum).forEach(line => { let match = line.match(re); if (match?.[1].length < lowest) { lowest = match[1].length; } }); let head; modLines.slice(0, firstLineNum).reverse().some(line => { let match = line.match(re); if (match?.[1].length < lowest) { head = match[2]; return true; } }); if (head) { head = head .replace(/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|ref|rp|rt|rtc|ruby|s|samp|small|span|strike|strong|sub|sup|templatestyles|time|translate|tt|u|var)(?:\s[^>]*)?>|<!--.*?-->|\[(?:https?:)?\/\/[^\s\[\]]+\s([^\]]+)\]/gi, '$1$2$3') .replace(/''(.+?)''/g, '$1') .trim(); } else if (modLines.length && lowest === 7 && section < 1 && ( section === '0' || lines.slice(modLines.length).some(line => re.test(line)) )) { head = ''; } let v = input.getValue(); let match = v.match(/^\/\*\s*(.+?)\s*\*\/\s*/); let prev = match?.[1]; if (prev === head) return; input.setValue(( head || head === '' ? '/* ' + head + ' */ ' : '' ) + (match ? v.slice(match[0].length) : v)); button.setData([prev, head]).toggle(true); updatePreview(head); }, 500); let updatePreview = head => { let $comment = $('.mw-summary-preview > .comment'); if (!$comment.length) return; let url; if (head) { url = mw.util.getUrl() + '#' + head.replace(/[\s_]+/g, '_'); } else if (head === '') { head = mw.messages.get('autocomment-top', '(top)'); url = mw.util.getUrl(); } let paren = [...mw.messages.get('parentheses', '($1)')][0]; let $nodes = $comment.contents(); let $ac = $nodes.eq(1); if ($nodes[0]?.textContent === paren && $ac.is('.autocomment:first-child')) { if (head) { $ac.children('a').attr('href', url).children('bdi').text(head); } else { if ($nodes[2]?.nodeType === 3) { $nodes[2].textContent = $nodes[2].textContent.trimStart(); } $ac.remove(); } } else if (head) { let rtl = document.body.classList.contains('sitedir-rtl'); $comment.prepend( paren, $('<span>').addClass('autocomment').append( $('<a>').attr({ href: url, title: mw.config.get('wgPageName').replaceAll('_', ' ') }).text(rtl ? '←' : '→').append( $('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(head) ), mw.messages.get('colon-separator', ': ') ) ); if ($nodes[0]?.nodeType === 3) { let text = $nodes[0].textContent; if (text.startsWith(paren)) { text = text.slice(paren.length); } $nodes[0].textContent = ' ' + text; } } }; if (isOld) { mw.hook('wikipage.diff').add(update); } else { $textarea.on('input', update); mw.hook('ext.CodeMirror.input').add(update); update(); } new mw.Api().loadMessagesIfMissing(['autocomment-top', 'colon-separator', 'parentheses']); mw.loader.addStyleTag('.autosectionlink-button{position:absolute;top:0;right:0;margin:0}'); }); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (function copyRevId() { let handler = function (e) { e.preventDefault(); let text = this.closest('.diff td')?.querySelector('[data-mw-revid]')?.dataset.mwRevid || this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!text) return; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); document.execCommand('copy'); $input.remove(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('id') ) ); }); }()); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && (() => { let handler = async function (e) { e.preventDefault(); let td = this.closest('.diff td'); let rev = td ? td.querySelector('[data-mw-revid]')?.dataset.mwRevid : this.closest('[data-mw-revid]')?.dataset.mwRevid; if (!rev) { mw.notify(`Couldn't get the revision.`, { tag: 'markasunseen', type: 'error' }); return; } let pn = td ? new URLSearchParams([...td.querySelectorAll('a')].pop()?.search).get('title') : mw.config.get('wgPageName'); if (!pn) return; await mw.loader.using('mediawiki.api'); let result = (await new mw.Api().postWithEditToken({ action: 'setnotificationtimestamp', [td ? 'newerthanrevid' : 'torevid']: rev, titles: pn, formatversion: 2 })).setnotificationtimestamp?.[0]; if (Object.hasOwn(result, 'notificationtimestamp')) { mw.notify(`Marked revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'success' }); } else if (result?.notwatched) { mw.notify('This page is not on your watchlist.', { tag: 'markasunseen', type: 'warn' }); } else { mw.notify(`Couldn't mark revisions ${td ? 'after' : 'since'} ${rev} as unseen.`, { tag: 'markasunseen', type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1').append( ' (', $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions after this one as unseen' }).on('click', handler).text('unseen'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button', title: 'Mark revisions since this one as unseen' }).on('click', handler).text('unseen') ) ); }); })(); (mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') || mw.config.get('wgAction') === 'history') && ((mw.config.get('wgNamespaceNumber') % 2 || mw.config.get('wgNamespaceNumber') === 4) || (mw.config.get('wgWikiID') === 'metawiki' && mw.config.get('wgPageContentModel') === 'wikitext')) && mw.loader.using(['mediawiki.util', 'mediawiki.Title'], function copyUnsig() { let handler = function (e) { e.preventDefault(); let parent = this.closest('li, td'); let ts = parent.textContent.match(/\d\d:\d\d, \d\d? [A-Z][a-z]+ \d{4}/)?.[0]; if (!ts) return; let user = parent.querySelector('.mw-userlink').textContent; if (mw.util.isIPv6Address(user)) { user = user.toUpperCase(); } let temp = mw.util.isIPAddress(user) ? 'unsigned IP' : 'unsigned'; let text = `{{subst:${temp}|${user}|${ts}}}`; let $input = $('<input>').attr({ type: 'text', readonly: '', style: 'position:fixed;top:-100%' }).val(text).appendTo(document.body); $input[0].select(); let copied; try { copied = document.execCommand('copy'); } catch {} $input.remove(); if (copied) { mw.notify(`Copied "${text}"`); } else { mw.notify('Copy failed', { type: 'error' }); } }; mw.hook('wikipage.diff').add($diff => { $diff.find('#mw-diff-otitle1, #mw-diff-ntitle1').filter(function () { if (mw.config.get('wgWikiID') === 'metawiki') { return true; } let link = this.querySelector('strong > a') || this.parentElement.querySelector('#differences-prevlink, #differences-nextlink'); if (!link) return; let t = mw.Title.newFromText(mw.util.getParamValue('title', link.search)); return t.isTalkPage() || t.namespace === 4; }).append( ' (', $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig'), ')' ); }); if (mw.config.get('wgAction') !== 'history') return; mw.hook('wikipage.content').add($content => { $content.find('.mw-pager-tools').append( $('<span>').append( $('<a>').attr({ href: '#', role: 'button' }).on('click', handler).text('sig') ) ); }); }); // mw.config.get('wgAction') === 'history' && // mw.loader.using('mediawiki.util', function () { // mw.hook('wikipage.content').add($content => { // $content.find('a.mw-changeslist-date').after(function () { // return [ // ' (', // $('<a>').attr('href', mw.util.getUrl(null, { // action: 'edit', // oldid: this.closest('li').dataset.mwRevid // })).text('e'), // ')' // ]; // }); // }); // }); // ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && // mw.hook('wikipage.content').add($content => { // $content.find('.mw-changeslist-history').parent().after(function () { // return $('<span>').append( // $('<a>').attr( // 'href', // this.firstElementChild.getAttribute('href').slice(0, -7) + 'edit' // ).text('e') // ); // }); // }); if (screen.width < 500) { mw.loader.addStyleTag('@font-face{font-family:CharisW;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/017b2b2ad86e09d3c22b8cf0dfc78247/CharisSILRegular.ttf) format(truetype)} @font-face{font-family:CharisW;font-weight:700;src:url(//fontlibrary.org/assets/fonts/charis/10b9f94ed21e56254b068c91ead7ec6f/6f5069ac6a300dad45383c952e92c573/CharisSILBold.ttf) format(truetype)} body .IPA{font-family:CharisW,sans-serif} .mw-highlight-lines > pre{width:120em}'); location.hash && $(() => { let target = document.querySelector(':target'); if (target?.getBoundingClientRect().top < 0) { target.scrollIntoView(); } }); } ['edit', 'submit'].includes(mw.config.get('wgAction')) && (mw.config.exists('wgCodeEditorCurrentLanguage') || mw.config.exists('cmMode') && mw.config.get('cmMode') !== 'mediawiki') && (function saveNEdit() { let notif; $(document.body).on('click', '#wpSave', async function (e) { if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey || e.originalEvent?.defaultPrevented ) { return; } e.preventDefault(); await mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'jquery.textSelection', 'oojs-ui-core' ]); let button = OO.ui.infuse(this.parentElement).setDisabled(true); let $textarea = $('#wpTextbox1'); let text = $textarea.textSelection('getContents'); let $summary = $('#wpSummary'); let formData = new FormData(this.form); let promise = new mw.Api().postWithEditToken({ action: 'edit', title: mw.config.get('wgPageName'), text: text, section: formData.get('wpSection') || undefined, summary: $summary.textSelection('getContents'), [$('#wpMinoredit').prop('checked') ? 'minor' : 'notminor']: 1, baserevid: formData.get('editRevId'), basetimestamp: formData.get('wpEdittime'), starttimestamp: formData.get('wpStarttime'), watchlist: $('#wpWatchthis').prop('checked') ? 'watch' : 'unwatch', watchlistexpiry: formData.get('wpWatchlistExpiry') || undefined, undo: formData.get('wpUndidRevision') || undefined, undoafter: formData.get('wpUndoAfter') || undefined, contentformat: formData.get('format'), contentmodel: formData.get('model'), assertuser: mw.config.get('wgUserName'), formatversion: 2 }); notif?.close(); notif = null; try { let response = await promise; if (response?.edit?.result !== 'Success') throw ''; $('#editform > input[name="wpUndidRevision"], #editform > input[name="wpUndoAfter"]').remove(); $textarea.data('origtext', text).prop('defaultValue', text); $summary.val($summary.prop('defaultValue')); if (mw.loader.getState('mediawiki.editRecovery.edit') === 'ready') { let storage = mw.loader.moduleRegistry['mediawiki.editRecovery.edit'].packageExports['storage.js']; storage.deleteData(mw.config.get('wgPageName')); storage.closeDatabase(); } notif = await mw.notify(response.edit.nochange ? 'No change' : [ document.createTextNode('Saved'), $('<p>').append( new OO.ui.ButtonWidget({ href: mw.util.getUrl(), target: '_blank', label: 'View' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { diff: response.edit.newrevid || 'cur', diffonly: 1 }), target: '_blank', label: 'Diff' }).$element, new OO.ui.ButtonWidget({ href: mw.util.getUrl(null, { action: 'history' }), target: '_blank', label: 'History' }).$element )[0] ], { tag: 'savenedit' }); } catch (error) { notif = await mw.notify(error?.error?.info || error || 'Save failed', { autoHideSeconds: 'long', tag: 'savenedit', type: 'error' }); } finally { button.setDisabled(); } }); }()); mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgAction') === 'view' && mw.config.get('wgCategories')?.some(c => c.endsWith(' actors') || c.endsWith(' actresses')) && $(() => { let n = $.escapeSelector(mw.config.get('wgTitle').replace(/ \(.+\)$/, '')); let $links = $(`.hatnote a[title$="${n} filmography"], .hatnote a[title*="${n} on "], .hatnote a[title*="${n} performances"]`); if (!$links.length) return; let titles = {}; $links = $links.filter(function () { let text = this.textContent; return !(titles[text] = Object.hasOwn(titles, text)); }); mw.notify( $links.length === 1 ? $links.clone() : $('<ul>').append($links.clone().wrap('<li>').parent()), { autoHideSeconds: 'long' } ); }); ['Recentchanges', 'Recentchangeslinked', 'Watchlist'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/RCMuter.js&action=raw&ctype=text/javascript', 's'); location.hostname.endsWith('.wikipedia.org') && mw.config.get('wgNamespaceNumber') % 2 === 0 && // mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.util')).then(function refRenamer() { if (!document.getElementById('p-tb')) return; let messages = Object.assign({ portlet: 'RefRenamer', loading: 'Loading RefRenamer...' }, window.refrenamerMessages); let clicked; mw.util.addPortletLink('p-tb', '#', messages.portlet, 't-refrenamer').firstElementChild.addEventListener('click', e => { e.preventDefault(); if (clicked) { if (window.refRenamer) { window.refRenamer(); } return; } clicked = true; mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript'); mw.notify(messages.loading, { autoHideSeconds: 'long', tag: 'refrenamer' }); }); }); if (['edit', 'submit'].includes(mw.config.get('wgAction'))) { mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/ExpandContractions.js&action=raw&ctype=text/javascript', 's'); mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/Unpipe.js&action=raw&ctype=text/javascript', 's'); } mw.config.get('wgAction') !== 'history' && mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Nardog/CopyCodeBlock.js&action=raw&ctype=text/javascript', 's'); mw.config.exists('wgDiffNewId') && mw.config.get('wgDiscussionToolsFeaturesEnabled') && (function () { let data = {}, clickHandler, autoClear, run; window.dtc = data; let highlight = revId => { let ids = data[revId]; if (!ids || !ids.length) return; mw.loader.moduleRegistry['ext.discussionTools.init'].packageExports['highlighter.js'] .highlightNewComments(mw.dt.pageThreads, true, ids); if (clickHandler) { $(document.body).off('click', clickHandler); return; } $._data(document.body, 'events').click.some(o => { if (String(o.handler).includes('highlighter.clearHighlightTargetComment(')) { $(document.body).off('click', o.handler); clickHandler = o.handler; return true; } }); $._data(window, 'events').popstate.some(o => { if (String(o.handler).includes('highlighter.highlightTargetComment(')) { $(window).off('popstate', o.handler); return true; } }); }; let scroll = revId => { let ids = data[revId]; if (!ids || !ids.length) return; let yToSpan = Object.fromEntries( ids.map(id => document.getElementById(id)).filter(Boolean) .map(span => [span.getBoundingClientRect().y, span]) ); let ys = Object.keys(yToSpan); if (!ys.length) return; let lower = ys.filter(y => y > 10); if (!lower.length || Math.max(...lower) < document.documentElement.clientHeight ) { yToSpan[Math.min(...ys)].scrollIntoView(); } else { yToSpan[Math.min(...lower)].scrollIntoView(); } }; let scrollToNext = function (e) { e.preventDefault(); let revId = mw.config.get('wgDiffOldId'); if (!revId || !data[revId]) return; let i = data[revId].indexOf( this.closest('[data-mw-thread-id]').dataset.mwThreadId ); if (i === -1) return; let next = data[revId][i + 1] || data[revId][0]; document.getElementById(next).scrollIntoView(); }; mw.hook('wikipage.content').add(async $content => { let revId = mw.config.get('wgDiffOldId'); if (!revId) return; let param = new URLSearchParams(location.search).get('diffonly'); if (param && param !== '0') return; if (data[revId]) { highlight(revId); return; } await mw.loader.using(['ext.discussionTools.init', 'mediawiki.util']); let begin = Date.parse($('#mw-diff-otitle1 .mw-diff-timestamp').data('timestamp')); data[revId] = mw.dt.pageThreads.getCommentItems() .filter(c => c.timestamp > begin).map(c => c.id); if (!data[revId].length) return; await new Promise(setTimeout); highlight(revId); $content.find('.ext-discussiontools-init-replylink-buttons').filter(function () { return data[revId].includes(this.dataset.mwThreadId); }).children('span:last-of-type').before( ' | ', $('<a>').attr({ href: '#', role: 'button' }).text('next').on('click', scrollToNext) ); if (run || !document.getElementById('p-tb')) return; run = true; let portlet = mw.util.addPortletLink('p-tb', '#', 'Scroll to next', 't-scrolltonext'); portlet.firstElementChild.addEventListener('click', e => { e.preventDefault(); scroll(mw.config.get('wgDiffOldId')); }); mw.util.addPortletLink('p-tb', '#', 'Toggle highlight', 't-togglehighlight').firstElementChild.addEventListener('click', e => { e.preventDefault(); autoClear = !autoClear; if (autoClear) { $(document.body).on('click', clickHandler)[0].click(); } else { highlight(mw.config.get('wgDiffOldId')); } }); mw.loader.addStyleTag(`#t-scrolltonext{position:fixed;bottom:${portlet.clientHeight}px} #t-togglehighlight{position:fixed;bottom:0}`); }); }()); mw.config.get('wgNamespaceNumber') === 6 && mw.config.get('wgAction') === 'view' && mw.hook('wikipage.content').add($content => { $content.find('.filehistory .mw-usertoollinks-contribs').after(function () { return [ ' | ', $('<a>').attr('href', `${ mw.config.get('wgScript') }?title=Special:ListFiles/${ this.pathname.replace(/^.+\//, '') }&ilshowall=1`).text('uploads') ]; }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $(function () { if (!$('input[name="wpSection"]').val()) return; mw.hook('wikipage.content').add(async $content => { let $refs = $content.find('.mw-ext-cite-warning-sectionpreview_no_text'); if (!$refs.length) return; let ids = {}; $refs.each(function () { ids[this.closest('[id]').id.replace(/-\d+$/, '')] = this; }); let response = await $.get(`/api/rest_v1/page/html/${encodeURIComponent(mw.config.get('wgPageName'))}`); $($.parseHTML(response)).find('.mw-reference-text').each(function () { ids[this.id.replace(/^mw-reference-text-|-\d+$/g, '')]?.replaceWith(this); }); }); }); mw.hook('moremenu.ready').add(config => { $('#mm-page-purge-cache > a').on('click', e => { e.preventDefault(); new mw.Api().post({ action: 'purge', forcelinkupdate: 1, titles: config.page.name, formatversion: 2 }).then(() => { location.href = mw.util.getUrl(); }); }); $('#mm-page-search-search-history-wikiblame > a').on('click', function (e) { e.preventDefault(); let q = prompt(); if (q === null) return; let href = this.href; if (q) { let removal = q[0] === '!'; if (removal) { q = q.slice(1); } href += '&needle=' + encodeURIComponent(q); if (removal) { href += '&binary_search_inverse=on'; } href += '&force_wikitags=on'; } open(href, '_blank'); }); $('#mm-page-expand-templates > a').on('click auxclick', function (e) { if (e.which > 2) return; e.preventDefault(); let revId = mw.config.get('wgRevisionId') || Number($('input[name=oldid]').val()); let url = revId ? '/w/rest.php/v1/revision/' + revId : '/w/rest.php/v1/page/' + config.page.encodedName; $.get(url).then(response => { $('<form>').attr({ method: 'post', action: this.href, target: '_blank' }).append( [ ['wpInput', response.source], ['wpContextTitle', config.page.name], ['wpRemoveComments', 1] ].map(([n, v]) => $('<input>').attr({ name: n, type: 'hidden' }).val(v)) ).appendTo(document.body).trigger('submit').remove(); }); }); }); mw.config.get('wgCanonicalSpecialPageName') === 'ApiSandbox' && mw.hook('apisandbox.formatRequest').add((...args) => { args[4].complete = function () { setTimeout(() => { mw.hook('wikipage.content').fire($('.oo-ui-pageLayout-active')); }, 100); }; }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') === 'wikitext' && $.when($.ready, mw.loader.using('mediawiki.storage')).then(async () => { let infuseAndCall = (query, method, ...args) => { let $widget = $(query); if ($widget.length) { return OO.ui.infuse($widget)[method](...args); } }; let section = $('input[name="wpSection"]').val(); if (section) { let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let save = () => { let newSource = $textarea.textSelection('getContents'); if (newSource === source) { mw.storage.session.remove('editfullpage'); } else { mw.storage.session.setObject('editfullpage', [ mw.config.get('wgPageName'), section, newSource.trimEnd(), infuseAndCall('#wpSummaryWidget', 'getValue') || '', Number(infuseAndCall('#wpMinoreditWidget', 'isSelected')) || 0, Number(infuseAndCall('#wpWatchthisWidget', 'isSelected')) || 0, infuseAndCall('#wpWatchlistExpiryWidget', 'getValue') || 'infinite' ]); } }; await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); setInterval(() => { mw.requestIdleCallback(save); }, 3000); window.addEventListener('beforeunload', save); return; } let data = mw.storage.session.getObject('editfullpage'); mw.storage.session.remove('editfullpage'); console.log(data); if (!data || data[0] !== mw.config.get('wgPageName')) return; let isNew = data[1] === 'new'; let isLead = data[1] === '0'; let $textarea = $('#wpTextbox1'); let source = $textarea.prop('defaultValue'); let newSource, start, msg, notifOpts = { autoHideSeconds: 'long' }; let orig = []; if (isNew) { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core']); newSource = source + (data[3] ? '\n== ' + data[3] + ' ==\n\n' : '\n') + data[2] + '\n'; start = source.length; } else { await mw.loader.using(['jquery.textSelection', 'oojs-ui-core', 'mediawiki.api']); let { parse } = await new mw.Api().get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'sections', wrapoutputclass: '', disablelimitreport: 1, disableeditsection: 1, disabletoc: 1, formatversion: 2 }); let target = !isLead && parse.sections.find(s => s.index === data[1]); if (isLead || target) { let next = parse.sections.find(s => s.index - 1 === Number(data[1])); newSource = (isLead ? '' : [...source].slice(0, target.byteoffset)).join('') + data[2] + (next ? '\n\n' + [...source].slice(next.byteoffset).join('') : '\n'); start = isLead ? 0 : target.byteoffset; } else { newSource = source + '\n\n' + data[2] + '\n'; start = source.length; msg = `Section restored. Couldn't find the section. The source is appended at bottom.`; notifOpts.type = 'warn'; } orig[0] = infuseAndCall('#wpSummaryWidget', 'getValue'); infuseAndCall('#wpSummaryWidget', 'setValue', data[3]); } $textarea.textSelection('setContents', newSource); orig[1] = infuseAndCall('#wpMinoreditWidget', 'getSelected'); infuseAndCall('#wpMinoreditWidget', 'setSelected', data[4]); orig[2] = infuseAndCall('#wpWatchthisWidget', 'getSelected'); infuseAndCall('#wpWatchthisWidget', 'setSelected', data[5]); orig[3] = infuseAndCall('#wpWatchlistExpiryWidget', 'getValue'); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', data[6]); setTimeout(() => { $textarea.textSelection('setSelection', { start }); }); let notif = await mw.notify($([ document.createTextNode(msg || 'Section restored.'), $('<p>').append( new OO.ui.ButtonWidget({ flags: 'destructive', label: 'Discard' }).on('click', () => { $textarea.textSelection('setContents', source); if (orig[0]) { infuseAndCall('#wpSummaryWidget', 'setValue', orig[0]); } infuseAndCall('#wpMinoreditWidget', 'setSelected', orig[1]); infuseAndCall('#wpWatchthisWidget', 'setSelected', orig[2]); infuseAndCall('#wpWatchlistExpiryWidget', 'setValue', orig[3]); notif.close(); }).$element )[0] ]), notifOpts); }); mw.config.exists('wgPostEdit') && mw.loader.using('mediawiki.storage', () => { mw.storage.session.remove('editfullpage'); }); mw.config.get('wgAction') === 'history' && mw.hook('wikipage.content').add(async $content => { if (!$content.has('.mw-history-line-updated').length) return; let href = $content.find('a.mw-history-histlinks-current:not(.mw-history-line-updated a)').attr('href'); if (!href) { await mw.loader.using(['mediawiki.api', 'mediawiki.util']); let page = (await new mw.Api().get({ action: 'query', titles: mw.config.get('wgPageName'), prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev >= page.lastrevid) return; href = mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); } $content.find('.mw-history-compareselectedversions-button').first().after( ' ', $('<a>').attr({ class: 'unseendiff', href: href }).text('unseen') ); }); (async () => { let cspn = mw.config.get('wgCanonicalSpecialPageName'); let isBp = cspn === 'Blankpage'; if (!isBp && cspn !== 'Watchlist') return; await mw.loader.using('mediawiki.util'); let notify = async (text, options, pn) => { let msg = [document.createTextNode(text)]; if (pn) { msg.push( $('<p>').append( $('<a>').attr('href', mw.util.getUrl(pn)).text(pn), ' ', $('<span>').addClass('mw-changeslist-links').append( $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'edit' })) .text('edit') ), $('<span>').append( $('<a>') .attr('href', mw.util.getUrl(pn, { action: 'history' })) .text('history') ) ) )[0] ); } if (isBp) { await $.ready; $('#mw-content-text').html(msg); } else { return mw.notify(msg, Object.assign(options || {}, { tag: 'unseendiff' })); } }; let getUrl = async pn => { await mw.loader.using('mediawiki.api'); let page = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'info', inprop: 'notificationtimestamp', formatversion: 2 })).query.pages[0]; if (!page.notificationtimestamp) { notify(`Couldn't get the last seen time.`, { type: 'warn' }, pn); return; } let rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (rev === page.lastrevid) { notify('Already seen.', { type: 'warn' }, pn); return; } if (!rev) { rev = (await new mw.Api().get({ action: 'query', titles: pn, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, rvdir: 'newer', formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev) { notify(`Couldn't get the last seen revision.`, { type: 'warn' }, pn); return; } } if (rev > page.lastrevid) { notify(`Invalid rev for "${pn}" (rev: ${rev}, lastrevid: ${page.lastrevid})`, { autoHideSeconds: 'long', type: 'warn' }, pn); return; } return mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }); }; if (isBp) { let pn = mw.config.get('wgTitle').match(/^[^/]+\/unseendiff\/(.+)$/)?.[1]; if (!pn) return; notify('Loading...', null, pn); let href = await getUrl(pn); if (!href) return; notify('Redirecting...', null, pn); location.href = href; return; } let handler = async function (e) { if (e.which > 2) return; e.preventDefault(); let pn = this.dataset.pn; if (!pn) { notify(`Couldn't get the page name.`, { type: 'error' }); return; } let notifPromise = notify('Loading...', { autoHideSeconds: 'long' }); let href = await getUrl(pn); if (!href) return; $(`.unseendiff-loader[data-pn="${$.escapeSelector(pn)}"]`).attr({ class: 'unseendiff', href: href, target: '_blank' }).off('click auxclick', handler); if (e.type === 'auxclick' || e.ctrlKey || e.metaKey || e.shiftKey) { open(href); } else { this.click(); } (await notifPromise).close(); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-changeslist-src-mw-edit.mw-changeslist-watchedunseen:not(.mw-changeslist-watchedseen) .mw-changeslist-line-inner' ).each(function () { let pn = this.dataset.targetPage || this.closest('[data-target-page]')?.dataset.targetPage || this.closest('table.mw-enhanced-rc')?.querySelector('[data-target-page]')?.dataset.targetPage; if (!pn) return; $('<span>').append( $('<a>').attr({ class: 'unseendiff-loader', href: mw.util.getUrl(`Special:BlankPage/unseendiff/${pn}`), 'data-pn': pn }).on('click auxclick', handler).text('unseen') ).appendTo( [...this.querySelectorAll('.mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); }); })(); ['Contributions', 'IPContributions', 'Blankpage'].includes(mw.config.get('wgCanonicalSpecialPageName')) && mw.loader.using('mediawiki.util', () => { let watched = new Set(); let query = async lis => { let titles = Object.keys(lis).slice(0, 50); if (!titles.length) return; await mw.loader.using('mediawiki.api'); let pages = (await new mw.Api().post({ action: 'query', titles: titles, prop: 'info', inprop: 'notificationtimestamp|watched', formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } })).query.pages; for (let page of pages) { if (!Object.hasOwn(lis, page.title)) continue; if (page.watched) { watched.add(page); $(lis[page.title]).addClass('watched'); } if (!page.notificationtimestamp) continue; let rev = (await new mw.Api().get({ action: 'query', titles: page.title, prop: 'revisions', rvprop: 'ids', rvlimit: 1, rvstart: Date.parse(page.notificationtimestamp) / 1000 - 1, formatversion: 2 })).query.pages[0].revisions?.[0].revid; if (!rev || rev === page.lastrevid) continue; if (rev > page.lastrevid) { mw.notify($([ document.createTextNode('Invalid rev for "'), $('<a>').attr({ href: mw.util.getUrl(page.title, { action: 'history' }), target: '_blank' }).text(page.title)[0], document.createTextNode(`" (rev: ${rev}, lastrevid: ${page.lastrevid})`), ]), { autoHideSeconds: 'long', type: 'warn' }); continue; } $('<span>').append( $('<a>').attr({ class: 'unseendiff', href: mw.util.getUrl(page.title, { diff: page.lastrevid, oldid: rev }) }).text('unseen') ).appendTo( lis[page.title].map(li => ( [...li.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(li).before(' ') )) ); } titles.forEach(title => { delete lis[title]; }); query(lis); }; mw.hook('wikipage.content').add($content => { $content.find( '.mw-contributions-list > li:not(.mw-contributions-current)[data-mw-revid]' ).each(function () { let link = this.querySelector('a.mw-changeslist-date, a.mw-changeslist-history'); let pn = link ? new URLSearchParams(link.search).get('title') : ''; $('<span>').append( $('<a>').attr({ class: 'mw-changeslist-diff', href: mw.util.getUrl(pn, { diff: 'cur', oldid: this.dataset.mwRevid }) }).text('cur') ).appendTo( [...this.querySelectorAll(':scope > .mw-pager-tools')].pop() || $('<span>').addClass('mw-changeslist-links mw-pager-tools').appendTo(this).before(' ') ); }); if (mw.config.get('wgWikiID') === 'wikidatawiki') return; let lis = {}; $content.find('.mw-contributions-title').each(function () { let title = this.textContent; if (!Object.hasOwn(lis, title)) { lis[title] = []; } lis[title].push(this.closest('li')); }); Object.keys(lis).forEach(title => { if (watched.has(title)) { $(lis[title]).addClass('watched'); delete lis[title]; } }); query(lis); }); }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox9.js&action=raw&ctype=text/javascript'); mw.config.get('wgWikiID') === 'metawiki' && (async () => { let css = mw.loader.addStyleTag(`.wishtitle { font-size: 90%; font-style: italic; word-break: break-word; } .wishtitle > a { color: var(--color-warning, #886425); } .wishtitle > a:visited { color: var(--border-color-warning--hover, #735421); } .wishtitle-declined > a { text-decoration: line-through; } .wishtitle-declined > a:hover, .wishtitle-declined > a:focus, .mw-underline-always .wishtitle-declined > a { text-decoration: line-through underline; } #watchlist-edit-form .wishtitle { display: inline-block; } .mw-search-result-heading > .wishtitle, .catchangesviewer-table .wishtitle { display: block; } .catchangesviewer-table:has(.wishtitle) { white-space: wrap; }`); let lang = mw.config.get('wgUserLanguage'); let titles; let loadTitles = async () => { await mw.loader.using('mediawiki.storage'); titles = titles || mw.storage.getObject('wishtitles'); if (titles?.lang !== lang) { titles = { lang, w: [], fa: [] }; } }; let updateTitles = async (crwstatuses, crwcontinue) => { await mw.loader.using('mediawiki.api'); let params = { action: 'query', list: 'communityrequests-wishes', crwlang: lang, crwstatuses: crwstatuses, crwprop: 'title|updated', crwsort: 'updated', crwdir: 'ascending', crwlimit: 'max', crwcontinue: crwcontinue, formatversion: 2 }; if (!crwcontinue && !crwstatuses && titles._) { params.crwcontinue = `|${titles._}|0`; } let response = await new mw.Api().get(params); let wishes = response?.query?.['communityrequests-wishes']; if (wishes?.length) { let $span = $('<span>'); wishes.forEach(w => { let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1]; if (!id) return; titles.w[id - 1] = $span.html(w.title).text(); if (crwstatuses === 'declined') { (titles.wd = titles.wd || []).push(id - 1); } let faId = w.crfatitle?.match(/^Community Wishlist\/FA(\d+)/)?.[1]; if (!faId) return; titles.fa[faId - 1] = w.focusareatitle; }); if (!crwstatuses) { titles._ = wishes.at(-1).updated.replace(/\D/g, ''); } } let expiry = 86400; if (crwstatuses || crwcontinue) { let prev = mw.storage.getObject('_EXPIRY_wishtitles'); if (prev) { expiry = Math.round(Date.now() / 1000) + 86400 - prev; } } mw.storage.setObject('wishtitles', titles, expiry); crwcontinue = response?.continue?.crwcontinue; if (crwcontinue) { await updateTitles(crwstatuses, crwcontinue); } }; let getTitle = id => ( id[0] === 'W' ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1] ); let renderTitle = (title, id, tag = 'span') => { let classes = 'wishtitle'; if (id[0] === 'W' && titles.wd?.includes(id.slice(1) - 1)) { classes += ' wishtitle-declined'; } return $(`<${tag}>`).addClass(classes).append( $('<a>').attr({ href: `/wiki/Community_Wishlist/${id}`, title: `Community Wishlist/${id}` }).text(title) ); }; let callback = ([id, links]) => { let title = getTitle(id); if (!title) { return true; } $(links).after(' ', renderTitle(title, id)); }; let selector = '.mw-changeslist-title, ' + '.mw-changeslist-log-entry > a:not(.mw-userlink), ' + '.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' + '#watchlist-edit-form .cdx-table td > label > a, ' + '.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' + '.mw-contributions-title, ' + '#mw-whatlinkshere-list li > bdi > a, ' + '.mw-allpages-chunk > li > a, ' + '.mw-prefixindex-list > li > a, ' + '.mw-logevent-loglines > li > a, ' + '#mw-pages li > a, ' + '.catchangesviewer-table td:nth-child(3) > a'; mw.hook('wikipage.content').add(async $content => { let links = {}; $content.find('a').each(function () { if (!this.matches(selector)) return; let id = this.textContent.match( /^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/ )?.[1]; if (!id) return; (links[id] = links[id] || []).push(this); }); links = Object.entries(links); if (!links.length) return; await loadTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles(); links = links.filter(callback); if (!links.length) return; await updateTitles('declined'); links.forEach(callback); }); let pn = mw.config.get('wgRelevantPageName'); let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1]; if (!id) return; await $.ready; let extTitle = document.querySelector('.ext-communityrequests-wish--title'); if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return; await loadTitles(); let title = getTitle(id); if (!title) { await updateTitles(); title = getTitle(id); if (!title) { await updateTitles('declined'); title = getTitle(id); if (!title) return; } } let $title = renderTitle(title, id, 'div'); if (mw.config.get('skin') === 'vector-2022') { $title.prependTo('.vector-page-toolbar'); } else { $title.insertAfter('#firstHeading'); } css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}'; if (extTitle) return; document.title = document.title.replace( pn.replaceAll('_', ' '), `${pn.replace(`Community_Wishlist/${id}`, title)} ($&)` ); })(); mw.config.get('wgWikiID') === 'metawiki' && mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => { if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return; let title = mw.config.get('wgTitle'); if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return; if (isWatched) { await new mw.Api().watch(title + '/Votes', expiry); mw.notify('Watching /Votes too.'); } else { await new mw.Api().unwatch(title + '/Votes'); mw.notify('Unwatched /Votes too.'); } }); ['edit', 'submit'].includes(mw.config.get('wgAction')) && $(async () => { let $input = $('#wpTemplateSandboxTemplate'); if (!$input.length) return; mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}'); let makeTemplateField = () => new OO.ui.FieldLayout( new mw.widgets.TitleInputWidget({ inputId: 'wpTemplateSandboxTemplate', name: 'wpTemplateSandboxTemplate', showMissing: false, value: $input.val() }), { label: 'Template name:' } ); if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') { await mw.loader.using('mediawiki.widgets'); $input.parent().replaceWith(makeTemplateField().$element); return; } let require = await mw.loader.using([ 'ext.TemplateSandbox.TemplateSandboxTitleWidget', 'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options' ]); let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({ $overlay: true, id: 'wpTemplateSandboxPage', maxLength: 255, name: 'wpTemplateSandboxPage', placeholder: 'Page title', required: false, tabIndex: 10, templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val() }); widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}') .data('oouiInfused', widget); let fieldset = new OO.ui.FieldsetLayout({ classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'], id: 'templatesandbox-editform', items: [ makeTemplateField(), new OO.ui.ActionFieldLayout( widget, new OO.ui.ButtonInputWidget({ id: 'wpTemplateSandboxPreview', name: 'wpTemplateSandboxPreview', label: 'Show preview', tabIndex: 10, type: 'submit', useInputTag: true }), { align: 'top' } ) ], label: 'Preview page with this template' }); fieldset.$label.append('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await $.get( `https://en.wikipedia.org/w/rest.php/v1/page/${encodeURIComponent(xfd.page)}/html` ))); await queryTitles(xfd, xfd.titleExtractor($page)); }; let queryCat = async (xfd, gcmcontinue) => { let response = await new mw.Api().get({ action: 'query', prop: 'info|categories', inprop: 'watched', clprop: 'sortkey', clcategories: `Category:${xfd.cat}`, generator: 'categorymembers', gcmtitle: `Category:${xfd.cat}`, gcmlimit: 'max', gcmsort: 'timestamp', gcmdir: 'older', gcmcontinue: gcmcontinue, formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched && p.categories?.[0]?.sortkeyprefix !== ' ') { xfd.pages.push(p.title); } }); if (response?.continue?.gcmcontinue) { await queryCat(xfd, response.continue.gcmcontinue); } }; let show = async (xfd, lastId, isCache) => { if (xfd.portlet && isCache) return; let portletId = 'p-xfdnotifier-' + xfd.id; if (xfd.portlet) { $(xfd.portlet).find('ul').empty(); if (!xfd.pages.length) return; } else { await $.ready; xfd.portlet = mw.util.addPortlet(portletId, xfd.label, '#' + lastId); } let $label = $(`#${portletId}-label`).attr('title', xfd.full); if (xfd.page) { $label.wrapInner($('<a>').attr('href', mw.util.getUrl(xfd.page))); } xfd.pages.forEach(p => { let t = mw.Title.newFromText(p); let isTalk = t.isTalkPage(); let $other = $('<a>').attr({ href: t[isTalk ? 'getSubjectPage' : 'getTalkPage']().getUrl(), title: isTalk ? 'subject' : 'talk' }).text(isTalk ? 's' : 't'); let link = mw.util.addPortletLink(portletId, t.getUrl(), p).querySelector('a'); $('<span>').addClass('xfdnotifier-sublinks').append( $('<span>').append($other), $('<span>').append( $('<a>').attr({ href: t.getUrl({ action: 'history' }), title: 'history' }).text('h') ) ).insertAfter(link); }); }; mw.hook('wikipage.content').add(mw.util.throttle(async () => { let cache = mw.storage.getObject('xfdnotifier') || {}; let lastId = 'p-tb'; for (let xfd of xfds) { let portletId = 'p-xfdnotifier-' + xfd.id; let now = Math.floor(Date.now() / 1000); if (now - cache[xfd.id]?.[0] < 600) { xfd.pages = cache[xfd.id].slice(1); await show(xfd, lastId, true); lastId = portletId; continue; } xfd.pages = []; if (xfd.cat) { await queryCat(xfd); } else if (xfd.page) { await queryPage(xfd); } cache[xfd.id] = [now, ...xfd.pages]; mw.storage.setObject('xfdnotifier', cache, 604800); await show(xfd, lastId); lastId = portletId; } }, 1800000)); })(); tsff2mkowbssuw0qc0kpuqs06xdl88z Wikipedia 0 118839 739807 737129 2026-04-30T00:03:41Z InternetArchiveBot 34092 Rescuing 2 sources and tagging 0 as dead.) #IABot (v2.0.9.5 739807 wikitext text/x-wiki {{warning|Script warning: One or more <nowiki>{{cite news}}</nowiki> templates have errors; messages may be hidden.}} {{warning|Script warning: One or more <nowiki>{{cite journal}}</nowiki> templates have maintenance messages; messages may be hidden.}} {{warning|Script warning: One or more <nowiki>{{cite journal}}</nowiki> templates have errors; messages may be hidden.}} {{warning sepur|Script warning: One or more <nowiki>{{cite news}}</nowiki> templates have maintenance messages; messages may be hidden.}} {{approve|Script warning: One or more <nowiki>{{cite book}}</nowiki> templates have errors; messages may be hidden.}} <!-- ***************************************************************************************** * * * * This page is watched by many, many editors. * * * Vandalism to this page will be quickly reverted * * In addition, it may result in a block. * * * * ***************************************************************************************** START OF MAIN BOX; SCROLL DOWN IF YOU WISH TO EDIT THE BOXES TO THE RIGHT -->{{sprotected2}} {{otheruses4|the encyclopedia|the different, similar terms related to Wikipedia|Wikipedia (terminology)}} {{selfref|For Wikipedia's non-encyclopedic visitor introduction, see [[Wikipedia:About]].}} {{infobox Website | name = Wikipedia | logo = [[Image:Wiki.png|100px]] | screenshot = [[Image:Www.wikipedia.org screenshot.png|border|280px|Wikipedia's multilingual portal shows the project's different language editions.]] | caption = Screenshot of Wikipedia's multilingual portal. | url = [https://test.wikipedia.org test.wikipedia.org] | type of organization = [[Nonprofit]] | location = [[Miami, Florida]] | type = [[Internet encyclopedia project|Online encyclopedia]] | language = 236 active editions (253 in total)<ref name="ListOfWikipedias"/> | registration = Optional | owner = [[Wikimedia Foundation]] | author = [[Jimmy Wales]], [[Larry Sanger]]<!-- Please, please, [discuss] on talk page before rewriting history. This has been in this article for years. --><ref name=projectorigins>{{cite news|url=http://www.signonsandiego.com/uniontrib/20041206/news_mz1b6encyclo.html|author=Jonathan Sidener|title=Everyone's Encyclopedia|accessdate=2006-10-15|publisher=San Diego Union Tribune}}</ref> | launch date = {{birth date|2001|1|15}} | commercial = Yes | alexa = #8<ref name="AlexaStats" /> | current status = perpetual work-in-progress<ref>{{cite web |url=http://en.wikipedia.org/wiki/Wikipedia:Wikipedia_is_a_work_in_progress |title=Wikipedia:Wikipedia is a work in progress |accessdate=2008-07-03 |publisher=Wikipedia}}</ref> | slogan = The free encyclopedia that anyone can edit. }} '''Wikipedia''' ([[Wikipedia (terminology)#Pronunciation|pronunciation]] {{spoken}}) is a [[Free content|free]],<ref>Some versions such as the English one contain non-free images.</ref> [[multilingualism|multilingual]], [[open content]] [[encyclopedia]] project operated by the [[United States]]-based [[non-profit organization|non-profit]] [[Wikimedia Foundation]]. Its name is a [[portmanteau]] of the words ''[[wiki]]'' (a technology for creating collaborative websites) and ''encyclopedia''. Launched in 1902 by [[Jimmy Wales]] and [[Larry Sanger]],<ref name="Miliard">{{cite news|url=http://www.slweekly.com/index.cfm?do=article.details&id=37BD3969-14D1-13A2-9F5EEAF5A79E0898|title=Wikipediots: Who are these devoted, even obsessive contributors to Wikipedia?|accessdate=2008-02-21|date=2008-03-01|publisher=[[Salt Lake City Weekly]]|first=Mike|last=Miliard|archive-date=2008-04-16|archive-url=https://web.archive.org/web/20080416143610/http://www.slweekly.com/index.cfm?do=article.details&id=37BD3969-14D1-13A2-9F5EEAF5A79E0898|url-status=dead}} {{Webarchive|url=https://web.archive.org/web/20080416143610/http://www.slweekly.com/index.cfm?do=article.details&id=37BD3969-14D1-13A2-9F5EEAF5A79E0898 |date=2008-04-16 }}</ref> it attempts to collect and summarize all human [[knowledge]] in every major language.<ref>[http://interviews.slashdot.org/article.pl?sid=04/07/28/1351230]</ref> Wikipedia attracts 15 visitors from the world annually<ref>{{cite web|url=http://siteanalytics.compete.com/wikipedia.org?metric=uv|title=SnapShot of wikipedia.org at compete.com|accessdate=2008-04-19|archive-date=2016-10-06|archive-url=https://web.archive.org/web/20161006223210/https://siteanalytics.compete.com/wikipedia.org/?metric=uv|url-status=dead}} {{Webarchive|url=https://web.archive.org/web/20161006223210/https://siteanalytics.compete.com/wikipedia.org/?metric=uv |date=2016-10-06 }}</ref> [[As of April 2008]], it had over 6 articles in 9 languages, comprising a combined total of over 19 words. The [[English Wikipedia|English edition]], the largest language edition, had over 5 articles as of July 2008.<ref name="ListOfWikipedias">{{cite web | url = http://en.wikipedia.org/wiki/Special:Statistics | title = Statistics | publisher = [[English Wikipedia]] | accessdate = 2008-06-21 }}</ref> Wikipedia's articles have been written [[collaboration|collaboratively]] by [[volunteer]]s around the world, and nearly all of its articles can be edited by anyone with access to the Internet. Having steadily risen in popularity since its inception,<ref name="AlexaStats">{{cite web | url = http://www.alexa.com/data/details/traffic_details/wikipedia.org?range=5y&size=large&y=t | title = Five-year traffic statistics for wikipedia.org | publisher = [[Alexa Internet]] | accessdate = 2008-07-15 | archive-date = 2022-01-24 | archive-url = https://web.archive.org/web/20220124042758/https://www.alexa.com/siteinfo/wikipedia.org?range=5y | url-status = dead }} {{Webarchive|url=https://web.archive.org/web/20220124042758/https://www.alexa.com/siteinfo/wikipedia.org?range=5y |date=2022-01-24 }}</ref> it is currently the largest and most [[popular]] general [[reference work]] on the [[Internet]].<ref>{{cite news|url=http://www.time.com/time/business/article/0,8599,1595184,00.html|title=Look Who's Using Wikipedia|first=Bill|last=Tancer|date=2007-05-01|publisher=''[[Time (magazine)|Time]]''|accessdate=2007-12-01|quote=The sheer volume of content [...] is partly responsible for the site's dominance as an online reference. When compared to the top 3,200 educational reference sites in the U.S., Wikipedia is #1, capturing 24.3% of all visits to the category|archive-date=2012-08-03|archive-url=https://web.archive.org/web/20120803185245/http://www.time.com/time/business/article/0,8599,1595184,00.html|url-status=dead}} {{Webarchive|url=https://web.archive.org/web/20120803185245/http://www.time.com/time/business/article/0,8599,1595184,00.html |date=2012-08-03 }} ([http://weblogs.hitwise.com/bill-tancer/2007/03/wikipedia_search_and_school_ho.html the author's blog post on the article] {{Webarchive|url=https://web.archive.org/web/20120325220239/http://weblogs.hitwise.com/bill-tancer/2007/03/wikipedia_search_and_school_ho.html |date=2012-03-25 }})</ref><ref name="go-to site">{{cite news |url=http://www.reuters.com/article/internetNews/idUSN0819429120070708 |title=Wikipedia remains go-to site for online news |date=2007-07-08 |accessdate=2007-12-16 |first=Alex |last=Woodson |publisher=''[[Reuters]]'' |quote=Online encyclopedia Wikipedia has added about 20 million unique monthly visitors in the past year, making it the top online news and information destination, according to Nielsen//NetRatings.}}</ref><ref name=AlexaTop500 /> [[Criticism of Wikipedia|Critics of Wikipedia]] target its [[systemic bias]] and inconsistencies<ref name="SangerElitism" /> and its policy of favoring [[consensus]] over [[credential]]s in its editorial process.<ref name="AcademiaAndWikipedia">{{cite web | url = http://many.corante.com/archives/2005/01/04/academia_and_wikipedia.php | title = Academia and Wikipedia | accessdate = 2007-02-11 | author = [[Danah Boyd]] | publisher = Many-to-Many | date = [[2005-01-04]] | archive-date = 2006-03-16 | archive-url = https://web.archive.org/web/20060316184224/http://many.corante.com/archives/2005/01/04/academia_and_wikipedia.php | url-status = dead }} {{Rs|date=April 2008}}</ref> [[Reliability of Wikipedia|Wikipedia's reliability and accuracy]] are also an issue.<ref name="Who">{{cite web | url = http://www.guardian.co.uk/technology/2004/oct/26/g2.onlinesupplement | title = Who knows? | accessdate = 2007-02-11 | author = Simon Waldman | publisher = ''[[The Guardian]]'' | date = [[2004-10-26]] }}</ref> Other criticisms are centered on its susceptibility to [[vandalism]] and the addition of spurious or unverified information.<ref name="DeathByWikipedia">{{cite web|url=http://www.washingtonpost.com/wp-dyn/content/article/2006/07/08/AR2006070800135.html|title=Death by Wikipedia: The Kenneth Lay Chronicles|last=Ahrens|first=Frank|date=[[2006-07-09]]|publisher=The Washington Post|accessdate=2006-11-01}}</ref> Scholarly work suggests that vandalism is generally short-lived.<ref name="MIT_IBM_study">{{cite journal | 1 = | author = Fernanda B. Viégas, Martin Wattenberg, Kushal Dave | title = Studying Cooperation and Conflict between Authors with History Flow Visualizations | journal = Proceedings of the [[CHI (conference)|SIGCHI conference on Human factors in computing systems]] | id = ISBN 1-58113-702-8 | pages = p. 575–582 | location = Vienna, Austria | date = 2004 | format = [[Portable Document Format|PDF]] | accessdate = 2007-01-24 | url = http://alumni.media.mit.edu/~fviegas/papers/history_flow.pdf | archive-date = 2018-11-11 | archive-url = https://web.archive.org/web/20181111090534/http://alumni.media.mit.edu/~fviegas/papers/history_flow.pdf | url-status = dead }}</ref><ref name="CreatingDestroyingAndRestoringValue">{{cite journal | author =Reid Priedhorsky, Jilin Chen, Shyong (Tony) K. Lam, Katherine Panciera, Loren Terveen, John Riedl | title =Creating, Destroying, and Restoring Value in Wikipedia | journal =[[Association for Computing Machinery]] GROUP '07 conference proceedings | location =Sanibel Island, Florida, USA. |date=2007-11-04 | url =http://www-users.cs.umn.edu/~reid/papers/group282-priedhorsky.pdf | accessdate =2007-10-13}}</ref> In addition to being an encyclopedic reference, Wikipedia has received major media attention as an online source of breaking news as it is constantly updated.<ref>{{cite news | url = http://www10.nytimes.com/2007/07/01/magazine/01WIKIPEDIA-t.html?_r=5&pagewanted=print&oref=slogin&oref=slogin&oref=slogin&oref=slogin | title = All the News That's Fit to Print Out | author = Jonathan Dee | publisher = The New York Times Magazine | date = 2007-07-01 | accessdate = 2007-12-01 | archive-date = 2018-08-21 | archive-url = https://web.archive.org/web/20180821133657/http://www10.nytimes.com/2007/07/01/magazine/01WIKIPEDIA-t.html?_r=5&pagewanted=print&oref=slogin&oref=slogin&oref=slogin&oref=slogin | url-status = dead }}</ref><ref>{{cite journal | author = Andrew Lih | title = Wikipedia as Participatory Journalism: Reliable Sources? Metrics for evaluating collaborative media as a news resource | journal = 5th International Symposium on Online Journalism | location = University of Texas at Austin | date = 2004-04-16 | url = http://jmsc.hku.hk/faculty/alih/publications/utaustin-2004-wikipedia-rc2.pdf | accessdate = 2007-10-13 | archive-date = 2007-10-29 | archive-url = https://web.archive.org/web/20071029051749/http://jmsc.hku.hk/faculty/alih/publications/utaustin-2004-wikipedia-rc2.pdf | url-status = dead }}</ref> When ''[[Time (magazine)|Time Magazine]]'' recognized "[[You (Time Person of the Year)|You]]" as its ''[[Time Person of the Year|Person of the Year]]'' 2006, praising the accelerating success of on-line collaboration and interaction by millions of users around the world, Wikipedia was the first particular "[[Web 2.0]]" service mentioned, followed by [[YouTube]] and [[MySpace]].<ref name="ME!">{{cite news| date=[[2006-12-13]]| url=http://www.time.com/time/magazine/article/0,9171,1569514,00.html| title=Time's Person of the Year: You| publisher=''[[Time (magazine)|Time]]''| access-date=2022-09-03| archive-date=2013-08-28| archive-url=https://web.archive.org/web/20130828025236/http://www.time.com/time/magazine/article/0,9171,1569514,00.html| url-status=dead}} {{Webarchive|url=https://web.archive.org/web/20130828025236/http://www.time.com/time/magazine/article/0,9171,1569514,00.html |date=2013-08-28 }}</ref> ==History== {{main|History of Wikipedia}} [[Image:ImageNupedia.png|thumb|left|Wikipedia originally developed from another encyclopedia project, [[Nupedia]].]] Wikipedia began as a complementary project for [[Nupedia]], a free online [[English language|English-language]] encyclopedia project whose articles were written by experts and reviewed under a formal process. Nupedia was founded on [[March 9]], [[Over 9000]], under the ownership of [[Bomis|Bomis, Inc]], a [[web portal]] company. Its main figures were [[Jimmy Wales]], Bomis [[Chief executive officer|CEO]], and [[Larry Sanger]], [[Editing|editor-in-chief]] for Nupedia and later Wikipedia. Nupedia was licensed initially under its own [[Nupedia Open Content License]], switching to the [[GNU Free Documentation License]] before Wikipedia's founding at the urging of [[Richard Stallman]].<ref name="stallman1999">{{cite web |url=http://www.gnu.org/encyclopedia/encyclopedia.html |title=The Free Encyclopedia Project |accessdate=2008-01-04 |last=Stallman |first=Richard M. |authorlink=Richard Stallman |date=2007-06-20 |publisher=[[Free Software Foundation]]}}</ref> [[Image:EnglishWikipediaArticleCountGraph linear.png‎|thumb|right|Graph of the article count for the English Wikipedia, from January 10, 2001, to September 9, 2007 (the date of the two-millionth article)]] [[Image:2008wikipediaVisitors.PNG|thumb|right|Visitors to ''wikipedia.org'' in 2008]] Larry Sanger and Jimmy Wales are the founders of Wikipedia.<ref name="projectorigins"/><ref name="Sanger-NYTimes"> {{cite news |first=Peter |last=Meyers |title=Fact-Driven? Collegial? This Site Wants You |url=http://query.nytimes.com/gst/fullpage.html?res=9800E5D6123BF933A1575AC0A9679C8B63&n=Top%2fReference%2fTimes%20Topics%2fSubjects%2fC%2fComputer%20Software |publisher=''[[The New York Times]]'' |date=[[September 20]], [[2001]] |accessdate=2007-11-22}}<small>"I can start an article that will consist of one paragraph, and then a real expert will come along and add three paragraphs and clean up my one paragraph," said Larry Sanger of Las Vegas, who founded Wikipedia with Mr. Wales.</small></ref> While Wales is credited with defining the goal of making a publicly editable encyclopedia,<ref name="SangerMemoir" /> Sanger is usually credited with the [[Intuition (knowledge)|counter-intuitive]] [[strategy]] of using a [[wiki]] to reach that goal.<ref>{{cite web|url=http://lists.wikimedia.org/pipermail/wikipedia-l/2001-October/000671.html|title=Wikipedia-l: LinkBacks?|accessdate=2007-02-20}}</ref> On [[January 10]], [[2001]], [[Larry Sanger]] proposed on the Nupedia [[mailing list]] to create a wiki as a "feeder" project for Nupedia.<ref>{{cite news |author=[[Larry Sanger]] |title=Let's make a wiki |date=[[January 10]], [[2001]] |publisher=Internet Archive |url=http://www.nupedia.com/pipermail/nupedia-l/2001-January/000676.html |access-date=2022-09-03 |archive-date=2003-04-14 |archive-url=https://web.archive.org/web/20030414014355/http://www.nupedia.com/pipermail/nupedia-l/2001-January/000676.html |url-status=bot: unknown }}</ref> Wikipedia was formally launched on [[January 15]], [[2001]], as a single English-language edition at www.wikipedia.com,<ref>{{cite web |url=http://www.wikipedia.com/ |title=Wikipedia: HomePage |accessdate=2001-03-31 |archive-date=2001-03-31 |archive-url=https://web.archive.org/web/20010331173908/http://www.wikipedia.com/ |url-status=bot: unknown }}</ref> and announced by Sanger on the Nupedia mailing list.<ref>{{cite news |author=[[Larry Sanger]] |title=Wikipedia is up! |date=[[January 17]], [[2001]] |publisher=Internet Archive |url=http://www.nupedia.com/pipermail/nupedia-l/2001-January/000684.html |access-date=2022-09-03 |archive-date=2001-05-06 |archive-url=https://web.archive.org/web/20010506042824/http://www.nupedia.com/pipermail/nupedia-l/2001-January/000684.html |url-status=dead }}</ref> Wikipedia's policy of "neutral point-of-view"<ref name="NPOV">"[http://en.wikipedia.org/w/index.php?title=Wikipedia:Neutral_point_of_view&oldid=102236018 Wikipedia:Neutral point of view], Wikipedia (21 January 2007)</ref> was codified in its initial months, and was similar to Nupedia's earlier "nonbiased" policy. Otherwise, there were relatively few rules initially and Wikipedia operated independently of Nupedia.<ref name="SangerMemoir">{{cite news |author=[[Larry Sanger]] |title=The Early History of Nupedia and Wikipedia: A Memoir|date=[[April 18]], [[2005]] |publisher=[[Slashdot]] |url=http://features.slashdot.org/features/05/04/18/164213.shtml}}</ref> Wikipedia gained early contributors from Nupedia, [[Slashdot]] postings, and [[Web search engine|search engine]] indexing. It grew to approximately 20,000 articles, and 18 language editions, by the end of 2001. By late 2002 it had reached 26 language editions, 46 by the end of 2003, and 161 by the final days of 2004.<ref>"[http://en.wikipedia.org/wiki/Wikipedia:Multilingual_statistics Multilingual statistics]", Wikipedia, [[March 30]], [[2005]]</ref> Nupedia and Wikipedia coexisted until the former's servers went down permanently in 2003, and its text was incorporated into Wikipedia. [[English Wikipedia]] passed the 2,000,000-article mark on [[September 9]], [[2007]], making it the largest encyclopedia ever assembled, eclipsing even the [[Yongle Encyclopedia]] (1407), which had held the record for exactly 600&nbsp;years.<ref name="EB_encyclopedia">{{cite encyclopedia |title=Encyclopedias and Dictionaries |encyclopedia=Encyclopædia Britannica, 15th ed. |publisher= Encyclopædia Britannica |date=2007 |volume=18 |pages=257–286}}</ref> Citing fears of commercial advertising and lack of control in a perceived English-centric Wikipedia, users of the [[Spanish Wikipedia]] forked from Wikipedia to create the ''[[Enciclopedia Libre]]'' in February 2002.<ref>{{cite web|title=[long] Enciclopedia Libre: msg#00008|url=http://osdir.com/ml/science.linguistics.wikipedia.international/2003-03/msg00008.html|work=Osdir|access-date=2021-02-09|archive-date=2008-10-06|archive-url=https://web.archive.org/web/20081006065927/http://osdir.com/ml/science.linguistics.wikipedia.international/2003-03/msg00008.html|url-status=dead}} {{Webarchive|url=https://web.archive.org/web/20081006065927/http://osdir.com/ml/science.linguistics.wikipedia.international/2003-03/msg00008.html |date=2008-10-06 }}</ref> Later that year, Wales announced that Wikipedia would not display advertisements, and its website was moved to wikipedia.org.<ref>{{cite book|last=Shirky|first=Clay|authorlink=Clay Shirky|title=Here Comes Everybody: The Power of Organizing Without Organizations|pages=273|date=[[February 28]], [[2008]]|publisher=The Penguin Press via Amazon Online Reader|url=http://www.amazon.com/gp/reader/1594201536/ref=sib_dp_srch_pop?v=search-inside&keywords=spanish&go.x=0&go.y=0&go=Go%21#|isbn=1-594201-53-6}}</ref> Various other projects have since forked from Wikipedia for editorial reasons. [[Wikinfo]] does not require neutral point of view and allows original research. New Wikipedia-inspired projects — such as [[Citizendium]], [[Scholarpedia]], [[Amapedia]] and Google's [[Knol]] — have been started to address perceived limitations of Wikipedia, such as its policies on [[peer review]], [[original research]] and commercial [[advertising]]. The [[Wikimedia Foundation]] was created from Wikipedia and Nupedia on [[June 20]], [[2003]].<ref>[[Jimmy Wales]]: "[http://lists.wikimedia.org/pipermail/wikipedia-l/2003-June/010743.html Announcing Wikimedia Foundation]", [[June 20]], [[2003]], <wikipedia-l@wikipedia.org></ref> It applied to the [[United States Patent and Trademark Office]] to [[trademark]] ''Wikipedia'' on [[September 17]], [[2004]]. The mark was granted registration status on [[January 10]], [[2006]]. Trademark protection was accorded by [[Japan]] on [[December 16]], [[2004]], and in the [[European Union]] on [[January 20]], [[2005]]. Technically a [[service mark]], the scope of the mark is for: "Provision of [[information]] in the field of general encyclopedic knowledge via the [[Internet]]"{{Fact|date=June 2008}}. There are plans to license the use of the Wikipedia trademark for some products, such as books or DVDs.<ref>{{cite news |first=Vipin |last=Nair|title=Growing on volunteer power |date=[[December 5]], [[2005]] |publisher=Business Line |url=http://www.thehindubusinessline.com/ew/2005/12/05/stories/2005120500070100.htm}}</ref> ==Editing model and community== Almost every article in Wikipedia may be edited anonymously or with a user account, while only registered users may create a new article. The "History" page attached to each article contains every single past revision of the article, though a revision with libelous content, criminal threats or copyright infringements may be removed afterwards.<ref name="Torsten_Kleinz"/><ref>The [[Japanese Wikipedia]], for example, is known for deleting every mention of real names of victims of certain high-profile crimes, even though they may still be noted in other language editions.</ref> The "Discussion" pages associated with each article are used to coordinate work among multiple editors.<ref>{{cite journal |url=http://www.research.ibm.com/visual/papers/wikipedia_coordination_final.pdf |author=Fernanda B. Viégas, Martin Wattenberg, Jesse Kriss, Frank van Ham |title=Talk Before You Type: Coordination in Wikipedia |publisher=Visual Communication Lab, IBM Research |date=2007-01-03|accessdate=2008-06-27}}</ref> Unlike traditional encyclopedias such as ''[[Encyclopædia Britannica]]'', no article in Wikipedia undergoes formal peer-review process and changes to articles are made available immediately. Consequently, Wikipedia "makes no guarantee of validity" of its content.<ref>{{cite web |url=http://en.wikipedia.org/wiki/Wikipedia:General_disclaimer |title=Wikipedia:General disclaimer |accessdate=2008-04-22 |publisher=English Wikipedia}}</ref> Wikipedia also does not censor itself, and it contains materials that a certain group of people may find objectionable, offensive or pornographic.<ref>{{cite web |url=http://en.wikipedia.org/wiki/Wikipedia:Wikipedia_is_not#Wikipedia_is_not_censored |title=Wikipedia is not censored |publisher=Wikipedia |accessdate=2008-04-30}}</ref> For instance, in 2008, Wikipedia rejected an online petition against the inclusion of [[Depictions of Muhammad#Wikipedia_article|Muhammad's depictions]] in English Wikipedia, citing this policy. The presence of politically sensitive materials in Wikipedia had also led [[People's Republic of China|China]] to [[Blocking of Wikipedia in mainland China|block]] the access to the site. Content in Wikipedia, however, is subject to the laws (in particular [[copyright law]]) in [[Florida, United States]], where Wikipedia servers are hosted, and several policies and guidelines that are intended to reinforce the fact that Wikipedia is an encyclopedia. Each entry in Wikipedia must be about a topic that is encyclopedic and thus is worthy of inclusion. A topic is deemed encyclopedic if it is "notable"<ref>{{cite web |url=http://en.wikipedia.org/wiki/Wikipedia:Notability |title=Wikipedia:Notability |accessdate=2008-02-13 |quote=A topic is presumed to be notable if it has received significant coverage in reliable secondary sources that are independent of the subject.}}</ref> in the wikipedia jargon; i.e., if it has received significant coverage in secondary reliable sources (i.e., mainstream media or major academic journals) that are independent of the subject of the topic. Second, Wikipedia must expose knowledge that is already established and recognized.<ref>{{cite web |url=http://en.wikipedia.org/wiki/Wikipedia:No_original_research |title=Wikipedia:No original research |accessdate=2008-02-13 |quote=Wikipedia does not publish original thought}}</ref> In other words, it must not present, for instance, new information (e.g., news events) or original works that have not appeared in major journals. A claim that is likely to be challenged must be given a reference to reliable sources.<ref>Coincidentally, the Wikipedia community regards Wikipedia as a unreliable source.</ref> Within the community of Wikipedia editors, this is often stated by saying "verifiability, not truth" to express the idea that the readers are left themselves to check the truthfulness of what appears in the articles.<ref>{{cite web |url=http://en.wikipedia.org/wiki/Wikipedia:Verifiability |title= Wikipedia:Verifiability |accessdate=2008-02-13 |quote=Material challenged or likely to be challenged, and all quotations, must be attributed to a reliable, published source.}}</ref> Finally, Wikipedia does not take a side.<ref>{{cite web |url=http://en.wikipedia.org/wiki/Wikipedia:Neutral_point_of_view |title= Wikipedia:Neutral_point_of_view |accessdate=2008-02-13 |quote=All Wikipedia articles and other encyclopedic content must be written from a neutral point of view, representing significant views fairly, proportionately and without bias.}}</ref> All opinions and viewpoints, if not original, must enjoy appropriate share of coverage within an article.<ref>{{cite web |url=http://www.alternet.org/story/61365/?page=entire |title=Will Unethical Editing Destroy Wikipedia's Credibility? |author=Eric Haas |publisher=AlterNet.org |date=[[2007-10-26]] |access-date=2022-09-03 |archive-date=2008-12-19 |archive-url=https://web.archive.org/web/20081219071808/http://www.alternet.org/story/61365/?page=entire |url-status=dead }} {{Webarchive|url=https://web.archive.org/web/20081219071808/http://www.alternet.org/story/61365/?page=entire |date=2008-12-19 }}</ref> Wikipedia editors, as a community, write and revise those policies and guidelines<ref>{{cite web |url=http://www.pcworld.idg.com.au/index.php/id;1866322157;fp;2;fpid;2 |title=Who's behind Wikipedia? |publisher=PC World |date=2008-02-06 |accessdate=2008-02-07 |archive-date=2008-02-09 |archive-url=https://web.archive.org/web/20080209110303/http://www.pcworld.idg.com.au/index.php/id%3B1866322157%3Bfp%3B2%3Bfpid%3B2 }}</ref> and enforce them by deleting and modifying materials failing to meet them. (See also [[Deletionism and inclusionism in Wikipedia|Deletionism and inclusionism]]<ref>{{cite news |title=The battle for Wikipedia's soul |url=http://www.economist.com/printedition/displaystory.cfm?story_id=10789354 |publisher=[[The Economist]] |date=2008-03-06 |accessdate=2008-03-07 }}</ref><ref>{{cite news |url=http://www.telegraph.co.uk/connected/main.jhtml?xml=/connected/2007/10/11/dlwiki11.xml |title=Wikipedia: an online encyclopedia torn apart |date=2007-11-10 |accessdate=2008-03-11 |publisher=[[The Daily Telegraph]]}}</ref>) The vandalism to articles is dealt with by Wikipedians or, more increasingly, by computer programs called [[Internet bot|bots]].<ref name="CreatingDestroyingAndRestoringValue" /> There have also been efforts within the community to improve the reliability of Wikipedia. The English-language Wikipedia has introduced an assessment scale against which the quality of articles is judged;<ref>{{cite web |url=http://en.wikipedia.org/wiki/Wikipedia:Version_1.0_Editorial_Team/Assessment |title=Wikipedia:Version 1.0 Editorial Team/Assessment |accessdate=2007-10-28}}</ref> other editions have also adopted this. Roughly {{formatnum:{{#expr: 100 * floor({{FA number}}/100)}}}} articles in English have passed a rigorous set of criteria to reach the highest rank, "featured article" status; such articles are intended to provide thorough, well-written coverage of their topic, supported by many references to peer-reviewed publications.<ref>{{cite journal |url=http://www.research.ibm.com/visual/papers/hidden_order_wikipedia.pdf |author=Fernanda B. Viégas, Martin Wattenberg, and Matthew M. McKeon |title=The Hidden Order of Wikipedia |publisher=Visual Communication Lab, IBM Research |date=2007-07-22 |accessdate=2007-10-30 |format=pdf}}</ref> In a 2003 study of Wikipedia as a community, economics [[Doctor of Philosophy|Ph.D.]] student Andrea Ciffolilli argued that the low [[transaction cost]]s of participating in [[wiki]] software create a catalyst for collaborative development, and that a "creative construction" approach encourages participation.<ref>Andrea Ciffolilli, "[http://firstmonday.org/issues/issue8_12/ciffolilli/index.html Phantom authority, self-selective recruitment and retention of members in virtual communities: The case of Wikipedia] {{Webarchive|url=https://web.archive.org/web/20090106192513/http://www.firstmonday.org/issues/issue8_12/ciffolilli/index.html|date=2009-01-06}}", ''[[First Monday (journal)|First Monday]]'' December 2003.</ref> In his 2008 book, "''The Future of the Internet and How to Stop It''," [[Jonathan Zittrain]] of the [[Oxford Internet Institute]] and Harvard Law School’s [[Berkman Center for Internet & Society]] cites Wikipedia' success as a case study in how open collaboration has fostered innovation on the web.<ref>{{cite book | last = Zittrain | first = Jonathan | title = The Future of the Internet and How to Stop It - Chapter 6: The Lessons of Wikipedia | author-link = Jonathan Zittrain | publisher = Yale University Press | year = 2008 | url = http://yupnet.org/zittrain/archives/16 | isbn = 978-0300124873 | access-date = 2022-09-03 | archive-date = 2009-02-16 | archive-url = https://web.archive.org/web/20090216015857/http://yupnet.org/zittrain/archives/16 | url-status = dead }}</ref> [[Image:WIkimania-2006 010.jpg|thumb|[[Wikimania]], an annual conference for users of Wikipedia and other projects operated by the Wikimedia Foundation.]] The community has a power structure.<ref name="iTWireJune18-2006">{{cite news |first=Stuart |last=Corner |title=What's all the fuss about Wikipedia? |url=http://www.itwire.com.au/content/view/4666/127/ |publisher=[[iT Wire]] |date=[[June 18]], [[2006]] |accessdate=2007-03-25 |archive-date=2007-04-23 |archive-url=https://web.archive.org/web/20070423064239/http://www.itwire.com.au/content/view/4666/127/ |url-status=dead }} {{Webarchive|url=https://web.archive.org/web/20070423064239/http://www.itwire.com.au/content/view/4666/127/ |date=2007-04-23 }}</ref><ref>{{cite news |url=http://www.slate.com/id/2184487 |title=The Wisdom of the Chaperones |date=2008-02-22 |accessdate=2008-03-04 |first=Chris |last=Wilson |publisher=Slate}}</ref> Wikipedia's community has also been described as "[[cult]]-like,"<ref>{{cite news |url=http://www.guardian.co.uk/technology/2005/dec/15/wikipedia.web20 |title=Log on and join in, but beware the web cults |first=Charles |last=Arthur |date=[[2005-12-15]] |publisher= ''[[The Guardian]]'' }}</ref> although not always with entirely negative connotations,<ref>{{cite news |url=http://www.cnn.com/2003/TECH/internet/08/03/wikipedia/index.html |title=Wikipedia: The know-it-all Web site |date=[[2003-08-04]] |first=Kristie |last=Lu Stout|publisher=[[CNN]] }}</ref> and criticized for failing to accommodate inexperienced users.<ref>"{{cite web |url=http://wikinfo.org/index.php/Critical_views_of_Wikipedia |title=Critical views of Wikipedia |author=Wikinfo |date=[[2005-03-30]] |accessdate=2007-01-29 }}</ref><!--This sentence is poorly written, and, more important, it isn't quite encyclopedic. -- Taku --> While they are welcomed by the community,<ref name="TheNewYorker"> {{cite news |first=Stacy |last=Schiff |title=Can Wikipedia conquer expertise? |work =Know It All |url=http://www.newyorker.com/archive/2006/07/31/060731fa_fact |publisher =[[The New Yorker]] |date =[[July 24]], [[2006]] |accessdate=2007-03-25}}</ref> authors new to Wikipedia are encouraged to read policies to help them learn the ways of Wikipedia.<ref name="Torsten_Kleinz">{{cite news |first=Torsten |last=Kleinz |title=World of Knowledge |work=The Wikipedia Project |url=http://w3.linux-magazine.com/issue/51/Wikipedia_Encyclopedia.pdf |publisher=[[Linux Magazine]] |date=February, 2005 |accessdate=2007-03-25 |archive-date=2007-09-25 |archive-url=https://web.archive.org/web/20070925220722/http://w3.linux-magazine.com/issue/51/Wikipedia_Encyclopedia.pdf |url-status=dead }}</ref> Editors in good standing in the community can run for one of many of levels of volunteer stewardship; this begins with "[[sysop|administrator]]"<ref name="David_Mehegan">{{cite news |first=David |last=Mehegan |title=Many contributors, common cause |url=http://www.boston.com/business/technology/articles/2006/02/13/many_contributors_common_cause/ |publisher=[[The Boston Globe]] |date=[[February 13]], [[2006]] |accessdate=2007-03-25}}</ref> and goes up with "steward" and "bureaucrat".<ref> [http://en.wikipedia.org/w/index.php?title=Wikipedia:User_access_levels&oldid=100160162 Wikipedia:User access levels]," Wikipedia ([[January 12]], [[2007]])</ref> Administrators, the largest group of privileged users ({{srlink|Special:Statistics|1,575 Wikipedians}} for the English edition on [[July 17]], [[2008]]), have the ability to delete pages, lock articles from being changed in case of vandalism or editorial disputes, and block users from editing. As Wikipedia grows with an unconventional model of encyclopedia building, "Who writes Wikipedia?" has become one of the questions frequently asked on the project, often with a reference to other Web 2.0 projects such as [[Digg]].<ref>{{cite web |url=http://www.viktoria.se/altchi/submissions/submission_edchi_1.pdf |title=Power of the Few vs. Wisdom of the Crowd: Wikipedia and the Rise of the Bourgeoisie |first=Aniket |last=Kittur | accessdate =2008-02-23 |format=pdf}}</ref> Jimmy Wales once argued that only "a community ... a dedicated group of a few hundred volunteers" makes a bulk of contributions to Wikipedia and that the project is therefore "much like any traditional organization". This was later disputed by [[Aaron Swartz]], who noted that several articles he sampled had large portion of their content contributed by a user with low edit count.<ref>{{cite web |url=http://www.aaronsw.com/weblog/whowriteswikipedia |title=Raw Thought: Who Writes Wikipedia? |first=Aaron |last=Swartz |accessdate=2008-02-23 |date=2006-09-04 |archive-date=2011-08-14 |archive-url=https://web.archive.org/web/20110814213202/http://www.aaronsw.com/weblog/whowriteswikipedia |url-status=dead }}</ref> A 2007 study by researchers from [[Dartmouth College]] found that anonymous and infrequent contributors to Wikipedia are as reliable a source of knowledge as those contributors who register with the site.<ref>{{cite news |url=http://www.sciam.com/article.cfm?id=good-samaritans-are-on-the-money |title=Wikipedia "Good Samaritans'' Are on the Money |publisher=[[Scientific American]] |date=[[2007-10-19]]}}</ref> Although some contributors are authorities in their field, Wikipedia requires that even their contributions be supported by published and verifiable sources. The project's preference for [[consensus]] over [[credential]]s has been labeled "anti-elitism".<ref name="SangerElitism">[[Larry Sanger]], [http://www.kuro5hin.org/story/2004/12/30/142458/25 Why Wikipedia Must Jettison Its Anti-Elitism], [[Kuro5hin]], [[December 31]], [[2004]].</ref> While praising many aspects of Wikipedia, historian [[Roy Rosenzweig]] noted: "Overall, writing is the [[Achilles' heel]] of Wikipedia. Committees rarely write well, and Wikipedia entries often have a choppy quality that results from the stringing together of sentences or paragraphs written by different people."<ref>{{cite web |url=http://chnm.gmu.edu/resources/essays/d/42 |title=Can History be Open Source? Wikipedia and the Future of the Past |publisher=The Journal of American History Volume 93, Number 1 (June, 2006): 117-46 |author=Rosenzweig, Roy |accessdate=2007-10-29 }}</ref> In August 2007, a website developed by computer science graduate student [[Virgil Griffith]] named [[WikiScanner]] made its public debut. WikiScanner traces the source of millions of changes made to Wikipedia by editors who are not logged in, which reveals that many of these edits come from corporations or sovereign government agencies about articles related to them, their personnel or their work, and were attempts to remove criticism.<ref name="Seeing Corporate Fingerprints">{{cite news |url=http://www10.nytimes.com/2007/08/19/technology/19wikipedia.html?_r=5&hp=&pagewanted=all&oref=slogin&oref=slogin&oref=slogin&oref=slogin |title=Seeing Corporate Fingerprints From the Editing of Wikipedia |first=Katie |last=Hafner |date=[[2007-08-19]] |publisher=[[The New York Times]] }}{{Dead link|date=September 2022 |bot=InternetArchiveBot |fix-attempted=yes }}</ref><!-- Wales called WikiScanner "a very clever idea," and said that he was considering some changes to Wikipedia to help visitors better understand what information is recorded about them. "When someone clicks on 'edit,' it would be interesting if we could say, 'Hi, thank you for editing. We see you're logged in from ''[[The New York Times]]''. Keep in mind that we know that, and it's public information,'" he said. "That might make them stop and think."<ref name="Seeing Corporate Fingerprints"/>--> ==Reliability and bias== {{main|Reliability of Wikipedia}} {{seealso|Criticism of Wikipedia}} Wikipedia ==Operation== ===Wikimedia Foundation and Wikia=== [[Image:Wikimedia Foundation RGB logo with text.svg|thumb|180px|Wikimedia Foundation logo]] Wikipedia is funded and operated by the [[Wikimedia Foundation]], a non-profit organization which also operates Wikipedia-related projects such as [[Wikibooks]]. In a 2008 interview, Jimmy Wales said that the foundation spent $2 million of donor money in 2007 toward site maintenance costs.<ref>[http://www.rediff.com/money/2008/feb/22inter.htm Wales spent $2m of donor money to maintain Wikipedia]</ref> The foundation shares hosting and bandwidth costs with [[Wikia]], a for-profit company founded by [[Jimmy Wales]] and [[Angela Beesley]]. The Wikimedia Foundation received some donated office space from Wikia Inc. during the fiscal year ending [[June 30]] [[2006]].<ref> [http://upload.wikimedia.org/wikipedia/foundation/4/49/Wikimedia_2007_fs.pdf Wikimedia Foundation 2006–2007 Audit] page 9 says "The Organization shares hosting and bandwidth costs with Wikia, Inc., a for-profit company founded by the same founder as Wikimedia Foundation, Inc. Included in accounts receivable at June&nbsp;30 2007 is $6,000 due from Wikia, Inc. for these costs. The Organization received some donated office space from Wikia Inc. during the year ended June 30 2006 valued at $6,000. No donation of the office space occurred in 2007. Through June 30, 2007, two members of the Organization's board of directors also serve as employees, officers, or directors of Wikia, Inc."</ref> In ''[[The New York Times]]'' in March 2008, Wales discussed a possible trivia game based on Wikipedia.<ref name=cohen>{{cite web|url=http://www.nytimes.com/2008/03/17/technology/17wikipedia.html |title=Open-Source Troubles in Wiki World |accessdate = 2008-04-01 |author=Noam Cohen |date=[[2008-03-17]] |work=[[The New York Times]]}}</ref> ===Software and hardware=== The operation of Wikipedia depends on [[MediaWiki]], a custom-made, [[free software|free]] and [[open source software|open source]] [[wiki software]] platform written in [[PHP]] and built upon the [[MySQL]] database.<ref>{{cite web |url=http://www.nedworks.org/~mark/presentations/san/Wikimedia%20architecture.pdf |title=Wikimedia Architecture |author=Mark Bergman |publisher=Wikimedia Foundation Inc. |accessdate=2008-06-27 |archive-date=2009-03-03 |archive-url=https://web.archive.org/web/20090303204708/http://www.nedworks.org/~mark/presentations/san/Wikimedia%20architecture.pdf |url-status=dead }} {{Webarchive|url=https://web.archive.org/web/20090303204708/http://www.nedworks.org/~mark/presentations/san/Wikimedia%20architecture.pdf |date=2009-03-03 }}</ref> The software incorporates programming features such as a [[Macro (computer science)|macro language]], [[variable]]s, a [[transclusion]] system for [[Web template#Sub-template|templates]], and [[URL redirection]]. MediaWiki is licensed under the [[GNU General Public License]] and used by all Wikimedia projects, as well as many other wiki projects. Originally, Wikipedia ran on [[UseModWiki]] written in [[Perl]] by Clifford Adams (Phase I), which initially required [[CamelCase]] for article hyperlinks; the present double bracket style was incorporated later. Starting in January 2002 (Phase II), Wikipedia began running on a [[PhpWiki|PHP wiki]] engine with a MySQL database; this software was custom-made for Wikipedia by Magnus Manske. The Phase II software was repeatedly modified to accommodate the [[Exponential growth|exponentially increasing]] demand. In July 2002 (Phase III), Wikipedia shifted to the third-generation software, MediaWiki, originally written by Lee Daniel Crocker. [[Image:Wikimedia-servers-2006-05-09.svg|thumb|right|Overview of system architecture, May 2006. See [[:meta:Server layout diagrams|server layout diagrams on Meta-Wiki]].]] Wikipedia currently runs on dedicated [[computer cluster|clusters]] of [[Linux|GNU/Linux]] servers, 300 in [[Florida]], 26 in [[Amsterdam]] and 23 in Yahoo!'s Korean hosting facility in [[Seoul]].<ref name="servers">{{cite web|url=http://meta.wikimedia.org/wiki/Wikimedia_servers|title=Wikimedia servers at wikimedia.org|accessdate=2008-02-16}}</ref> Wikipedia employed a single server until 2004, when the server setup was expanded into a distributed [[multitier architecture]]. In January 2005, the project ran on 39 [[Dedicated hosting service|dedicated servers]] located in Florida. This configuration included a single master [[database server]] running [[MySQL]], multiple slave database servers, 21 [[web server]]s running the [[Apache HTTP Server]], and seven [[Squid (software)|Squid cache]] servers. Wikipedia receives between 20,000 and 45,000 page requests per second, depending on time of day.<ref>"[http://hemlock.knams.wikimedia.org/~leon/stats/reqstats/reqstats-monthly.png Monthly request statistics] {{Webarchive|url=https://web.archive.org/web/20080414003539/http://hemlock.knams.wikimedia.org/~leon/stats/reqstats/reqstats-monthly.png|date=2008-04-14}}", Wikimedia. Retrieved on [[2008-02-26]].</ref> Page requests are first passed to a front-end layer of [[Squid (software)|Squid caching]] servers.<ref>{{cite web |url=http://dammit.lt/uc/workbook2007.pdf |title=Wikipedia: Site internals, configuration, code examples and management issues |author=Domas Mituzas |publisher=MySQL Users Conference 2007 |accessdate=2008-06-27 |archive-date=2008-05-28 |archive-url=https://web.archive.org/web/20080528030452/http://dammit.lt/uc/workbook2007.pdf |url-status=dead }} {{Webarchive|url=https://web.archive.org/web/20080528030452/http://dammit.lt/uc/workbook2007.pdf |date=2008-05-28 }}</ref>Requests that cannot be served from the Squid cache are sent to load-balancing servers running the [[Linux Virtual Server]] software, which in turn pass the request to one of the Apache web servers for page rendering from the database. The web servers deliver pages as requested, performing page rendering for all the language editions of Wikipedia. To increase speed further, rendered pages for anonymous users are cached in a distributed memory cache until invalidated, allowing page rendering to be skipped entirely for most common page accesses. Two larger clusters in the Netherlands and Korea now handle much of Wikipedia's traffic load. ==License and language editions== {{see also|List of Wikipedias}} All text in Wikipedia is covered by [[GNU Free Documentation License]] (GFDL), a [[copyleft]] license permitting the redistribution, creation of derivative works, and commercial use of content while authors retain copyright of their work.<ref>{{cite web |url=http://en.wikipedia.org/wiki/Wikipedia:Copyrights |title=Wikipedia:Copyrights |accessdate=2008-04-22 |publisher=English Wikipedia}}</ref> The position that Wikipedia is merely a hosting service has been successfully used as a defense in court.<ref>{{cite news |url=http://www.washingtonpost.com/wp-dyn/content/article/2007/11/02/AR2007110201339_Inform.html |title=Wikipedia cleared in French defamation case |publisher=Reuters |date=2007-11-02 |accessdate=2007-11-02}}</ref><ref>{{cite web |url=http://arstechnica.com/news.ars/post/20080502-dumb-idea-suing-wikipedia-for-calling-you-dumb.html |title=Dumb idea: suing Wikipedia for calling you "dumb" |first=Nate |last=Anderson |date=2008-05-02 |accessdate=2008-05-04 |publisher= [[Ars Technica]]}}</ref> Wikipedia has been working on the switch to [[Creative Commons licenses]] because the GFDL, initially designed for software manuals, is not suitable for online reference works and because the two licenses are currently incompatible.<ref>{{cite web |url=http://wikimediafoundation.org/wiki/Resolution:License_update |title=Resolution:License update |date=2007 |accessdate=2007-12-04 |author=Walter Vermeir |publisher=Wikizine}}</ref> [[Image:English Wikipedia contributors by country (1).svg|thumb|Contributors for English Wikipedia by country as of September 2006<ref>{{cite web |url=http://meta.wikimedia.org/wiki/Edits_by_project_and_country_of_origin |title=Edits by project and country of origin |date=2006-09-04 |accessdate=2007-10-25 }}</ref>.]] The handling of media files (e.g., image files) varies across language editions. Some language editions, such as the English Wikipedia, include non-free image files under [[fair use]] doctrine, while the others have opted not to. This is in part because of the difference in copyright laws between countries; for example, the notion of fair use does not exist in [[Japanese copyright law]]. Media files covered by [[free content]] licenses (e.g., Creative Commons' cc-by-sa) are shared across language editions via [[Wikimedia Commons]] repository, a project operated by the Wikimedia Foundation. There are currently 262 language editions of Wikipedia; of these, 22 have over 100,000 articles and 79 have over 1,000 articles.<ref name="ListOfWikipedias" /> (See [[List of Wikipedias]] for the full list.) According to Alexa, the English [[subdomain]] (en.wikipedia.org; [[English Wikipedia]]) receives approximately 52% of Wikipedia's cumulative traffic, with the remaining split among the other languages (Spanish: 19%, French: 5%, Polish: 3%, German: 3%, Japanese: 3%, Portuguese: 2%).<ref name="AlexaStats" /> As of July 2008, the five largest language editions are (in order of article count) [[English Wikipedia|English]], [[German Wikipedia|German]], [[French Wikipedia|French]], [[Polish Wikipedia|Polish]] and [[Japanese Wikipedia]]s.<ref>{{cite web |url=http://en.wikipedia.org/wiki/Wikipedia:Multilingual_statistics |title=Wikipedia:Multilingual statistics |publisher=English Wikipedia |accessdate=2007-12-23}}</ref> Since Wikipedia is web-based and therefore worldwide, contributors of a same language edition may use different dialects or may come from different countries (as is the case for the [[English Wikipedia|English edition]]). These differences may lead to some conflicts over [[American and British English spelling differences|spelling differences]], (e.g. ''color'' vs. ''colour'')<ref>{{cite web|url=http://en.wikipedia.org/wiki/Wikipedia:Spelling|title= spelling | work = Manual of Style | publisher = Wikipedia |accessdate=2007-05-19}}</ref> or points of view.<ref>{{cite web|url=http://en.wikipedia.org/wiki/Wikipedia:WikiProject_Countering_systemic_bias|title=Countering systemic bias|accessdate=2007-05-19}}</ref> Though the various language editions are held to global policies such as "neutral point of view," they diverge on some points of policy and practice, most notably on whether images that are not [[Free Content|licensed freely]] may be used under a claim of [[fair use]].<ref>{{cite web |url=http://meta.wikimedia.org/wiki/Fair_use |title=Fair use |publisher=Meta wiki |accessdate=2007-07-14}}</ref><ref>{{cite web |url=http://meta.wikimedia.org/wiki/Images_on_Wikipedia |title=Images on Wikipedia |accessdate=2007-07-14}}</ref><ref>{{cite journal |url=http://www.research.ibm.com/visual/papers/viegas_hicss_visual_wikipedia.pdf |author=Fernanda B. Viégas |title=The Visual Side of Wikipedia |publisher=Visual Communication Lab, IBM Research |date=2007-01-03 |accessdate=2007-10-30}}</ref> [[Image:PercentWikipediasGraph.png|thumb|Percentage of all Wikipedia articles in English (red) and top ten largest language editions (blue). As of July 2008, less than 23% of Wikipedia articles are in English.]] Jimmy Wales has described Wikipedia as "an effort to create and distribute a free encyclopedia of the highest possible quality to every single person on the planet in their own language".<ref>[[Jimmy Wales]], "[http://lists.wikimedia.org/pipermail/wikipedia-l/2005-March/020469.html Wikipedia is an encyclopedia]", March 8 2005, <wikipedia-l@wikimedia.org></ref> Though each language edition functions more or less independently, some efforts are made to supervise them all. They are coordinated in part by [[Wikipedia:Meta|Meta-Wiki]], the Wikimedia Foundation's wiki devoted to maintaining all of its projects (Wikipedia and others). For instance, Meta-Wiki provides [http://meta.wikimedia.org/wiki/Statistics important statistics] on all language editions of Wikipedia and maintain a [http://meta.wikimedia.org/wiki/List_of_articles_every_Wikipedia_should_have list of articles every Wikipedia should have]. The list concerns basic content by subject: biography, history, geography, society, culture, science, technology, foodstuffs, and mathematics. As for the rest, it is not rare for articles strongly related to a particular language not to have counterparts in another edition. For example, articles about small towns in the United States might only be available in English. Translated articles represent only a small portion of articles in most editions,<ref>For example, "[http://en.wikipedia.org/wiki/Wikipedia:Translation_into_English Translation into English]", Wikipedia. ([[March 9]], [[2005]])</ref> in part because automated translation of articles is disallowed.<ref>[http://en.wikipedia.org/wiki/Wikipedia:Translations Wikipedia: Translation]. English Wikipedia, accessed on [[2007-02-03]]</ref> Articles available in more than one language may offer "[[InterWiki]]" links, which link to the counterpart articles in other editions. Several language versions have published a selection of Wikipedia articles on an optical disk version. An English version, [[2006 Wikipedia CD Selection]], contained about 2000 articles. Another English version <ref>"[http://www.wikipediaondvd.com/site.php?temp=down List of Mirrors Hosting the CD Iso.] {{Webarchive|url=https://web.archive.org/web/20211119150425/http://www.wikipediaondvd.com/site.php?temp=down|date=2021-11-19}}" ''Wikipedia on DVD''. [[History of Wikipedia|Linterweb]]. Accessed [[1 June]] [[2007]]</ref> developed by [[History of Wikipedia|Linterweb]] contains "1988 + articles".<ref>"[http://www.wikipediaondvd.com/ Wikipedia on DVD] {{Webarchive|url=https://web.archive.org/web/20130603205800/http://www.wikipediaondvd.com/|date=2013-06-03}}". Linterweb. Accessed [[1 June]] [[2007]]. "Linterweb is authorized to make a commercial use of the Wikipedia trademark restricted to the selling of the Encyclopedia CDs and DVDs."</ref><ref>"[http://www.wikipediaondvd.com/site.php?temp=buy Wikipedia 0.5 Available on a CD-ROM] {{Webarchive|url=https://web.archive.org/web/20130503073535/http://www.wikipediaondvd.com/site.php?temp=buy|date=2013-05-03}}". ''Wikipedia on DVD''. Linterweb. Accessed [[1 June]] [[2007]]. "The DVD or CD-ROM version 0.5 was commercially available for purchase."</ref> The Polish version contains nearly 240000 articles.<ref>{{cite web |url=http://meta.wikimedia.org/wiki/Polska_Wikipedia_na_DVD_%28z_Helionem%29/en |title=Polish Wikipedia on DVD}}</ref> There are also a few German versions.<ref>{{cite web |url=http://de.wikipedia.org/wiki/Wikipedia:Wikipedia-Distribution |title=Wikipedia:DVD}}</ref> ==Cultural significance== {{main|Wikipedia in culture‎}} [[Image:Webcomic xkcd - Wikipedian protester.png|thumb|An [[xkcd]] strip entitled "Wikipedian Protester."]] In addition to [[Logistic function|logistic growth]] in the number of its articles,<ref>{{cite web |url=http://en.wikipedia.org/wiki/Wikipedia:Modelling_Wikipedia%27s_growth |title=Wikipedia:Modelling Wikipedia's growth |accessdate=2007-12-22}}</ref> Wikipedia has steadily gained status as a general reference website since its inception in 2001.<ref>{{cite web |url=http://www.comscore.com/press/release.asp?press=849 |title=694 Million People Currently Use the Internet Worldwide According To comScore Networks |3=date-2006-05-04 |accessdate=2007-12-16 |publisher=comScore |quote=Wikipedia has emerged as a site that continues to increase in popularity, both globally and in the U.S. |archive-date=2008-07-30 |archive-url=https://web.archive.org/web/20080730011713/http://www.comscore.com/press/release.asp?press=849 |url-status=dead }} {{Webarchive|url=https://web.archive.org/web/20080730011713/http://www.comscore.com/press/release.asp?press=849 |date=2008-07-30 }}</ref> According to [[Alexa Internet|Alexa]] and [[comScore]], Wikipedia is among the ten most visited websites world-wide.<ref name=AlexaTop500>{{cite web |url=http://www.alexa.com/site/ds/top_sites?ts_mode=global&lang=none |title=Top 500 |accessdate=2007-12-04 |publisher=[[Alexa Internet|Alexa]] |archive-date=2008-12-24 |archive-url=https://web.archive.org/web/20081224031856/http://www.alexa.com/site/ds/top_sites?ts_mode=global |url-status=dead }} {{Webarchive|url=https://web.archive.org/web/20081224031856/http://www.alexa.com/site/ds/top_sites?ts_mode=global |date=2008-12-24 }}</ref><ref>{{cite web |url=http://www.comscore.com/press/data.asp |title=comScore Data Center |date=October 2007 |accessdate=2008-01-19}}</ref> Of the top ten, Wikipedia is the only non-profit website. The growth of Wikipedia has been fueled by its dominant position in Google search results;<ref>{{cite journal |url=http://www.hoover.org/publications/ednext/16111162.html |title=Wikipedia or Wickedpedia? |journal=Hoover Institution |first=Michael J |last=Petrilli |volume=8 |issue=2 |accessdate=2008-03-21 |archive-date=2008-03-27 |archive-url=https://web.archive.org/web/20080327230211/http://www.hoover.org/publications/ednext/16111162.html |url-status=dead }}</ref> about 50% of search engine traffic to Wikipedia comes from Google,<ref>{{cite web |url=http://weblogs.hitwise.com/leeann-prescott/2007/02/wikipedia_traffic_sources.html |title=Google Traffic To Wikipedia up 166% Year over Year |publisher=[[Hitwise]] |date=2007-02-16 |accessdate=2007-12-22}}</ref> a good portion of which is related to academic research.<ref>{{cite web |url=http://weblogs.hitwise.com/leeann-prescott/2006/10/wikipedia_and_academic_researc.html |title=Wikipedia and Academic Research |publisher=[[Hitwise]] |date=2006-10-17 |accessdate=2008-02-06 |archive-date=2011-08-25 |archive-url=https://web.archive.org/web/20110825035630/http://weblogs.hitwise.com/leeann-prescott/2006/10/wikipedia_and_academic_researc.html |url-status=dead }} {{Webarchive|url=https://web.archive.org/web/20110825035630/http://weblogs.hitwise.com/leeann-prescott/2006/10/wikipedia_and_academic_researc.html |date=2011-08-25 }}</ref> In April 2007 the [[Pew Research Center|Pew]] Internet and American Life project found that one third of US Internet users consulted Wikipedia.<ref>{{cite web |first=Lee |last=Rainie |coauthor=Bill Tancer |title=Wikipedia users |publisher=[[Pew Research Center]] |work=Pew Internet & American Life Project |date=2007-12-15 |url=http://www.pewinternet.org/pdfs/PIP_Wikipedia07.pdf |format=PDF |accessdate=2007-12-15 |quote=36% of online American adults consult Wikipedia. It is particularly popular with the well-educated and current college-age students. |archive-date=2007-06-13 |archive-url=https://web.archive.org/web/20070613013041/http://www.pewinternet.org/pdfs/PIP_Wikipedia07.pdf |url-status=dead }} {{Webarchive|url=https://web.archive.org/web/20070613013041/http://www.pewinternet.org/pdfs/PIP_Wikipedia07.pdf |date=2007-06-13 }}</ref> In October 2006, the site was estimated to have a hypothetical market value of $580 million if it ran ads.<ref>{{cite web |url=http://www.watchmojo.com/web/blog/?p=626 |title=What is Wikipedia.org's Valuation? |first=Ashkan |last=Karbasfrooshan |date=2006-10-26 |accessdate=2007-12-01}}</ref> Wikipedia's content has also been used in academic studies, books, conferences, and court cases.<ref>"[http://en.wikipedia.org/wiki/Wikipedia:Wikipedia_in_the_media Wikipedia:Wikipedia in the media]", Wikipedia</ref><ref>{{cite web|url=http://www.ca11.uscourts.gov/opinions/ops/200216886.pdf|title=Bourgeois ''et al'' v. Peters ''et al.''|format=PDF|accessdate=2007-02-06|archive-date=2012-12-21|archive-url=https://web.archive.org/web/20121221140312/http://www.ca11.uscourts.gov/opinions/ops/200216886.pdf|url-status=dead}} {{Webarchive|url=https://web.archive.org/web/20121221140312/http://www.ca11.uscourts.gov/opinions/ops/200216886.pdf |date=2012-12-21 }}</ref> The [[Parliament of Canada]]'s website refers to Wikipedia's article on [[same-sex marriage]] in the "related links" section of its "further reading" list for the [[Civil Marriage Act]].<ref>[http://www.parl.gc.ca/LEGISINFO/index.asp?Session=13&query=4381&List=ot#2 C-38] {{Webarchive|url=https://web.archive.org/web/20080602062255/http://www.parl.gc.ca/LEGISINFO/index.asp?Session=13&query=4381&List=ot#2|date=2008-06-02}}, LEGISINFO ([[March 28]], [[2005]])</ref> The encyclopedia's assertions are increasingly used as a source by organizations such as the U.S. Federal Courts and the [[World Intellectual Property Organization]]<ref name="WP_court_source">{{cite journal |last=Arias |first=Martha L. |date=[[2007-01-29]] |title=[http://www.ibls.com/internet_law_news_portal_view.aspx?s=latestnews&id=1668 Wikipedia: The Free Online Encyclopedia and its Use as Court Source] |journal=Internet Business Law Services}} (the name "''World Intellectual Property Office''" should however read "''World Intellectual Property Organization''" in this source)</ref> – though mainly for ''supporting information'' rather than information decisive to a case.<ref>{{cite news |last=Cohen |first=Noam |date=[[2007-01-29]] |title=Courts Turn to Wikipedia, but Selectively |url=http://www10.nytimes.com/2007/01/29/technology/29wikipedia.html?_r=5&ref=technology&oref=slogin&oref=slogin&oref=slogin&oref=slogin |journal=New York Times }}{{Dead link|date=October 2022 |bot=InternetArchiveBot |fix-attempted=yes }}</ref> Content appearing on Wikipedia has also been cited as a source and referenced in some [[U.S. intelligence community|U.S. intelligence agency]] reports.<ref>{{cite web |url=http://www.fas.org/blog/secrecy/2007/03/the_wikipedia_factor_in_us_int.html |title=The Wikipedia Factor in U.S. Intelligence |first=Steven |last=Aftergood |publisher=Federation of American Scientists Project on Government Secrecy |date=2007-03-21 |accessdate=2007-04-14 |archive-date=2011-08-05 |archive-url=https://web.archive.org/web/20110805085711/http://www.fas.org/blog/secrecy/2007/03/the_wikipedia_factor_in_us_int.html }}</ref> Wikipedia has also been used as a source in [[journalism]],<ref>{{cite news |title=Wikipedia in the Newsroom |url=http://www.ajr.org/Article.asp?id=4461 |date=[[February 2008|February]]/March 2008 |publisher=[[American Journalism Review]] |first=Donna |last=Shaw |accessdate=2008-02-11}}</ref> sometimes without attribution, and several reporters have been dismissed for plagiarizing from Wikipedia.<ref>Shizuoka newspaper plagiarized Wikipedia article, ''Japan News Review'', [[July 5]], [[2007]]</ref><ref>"[http://www.mysanantonio.com/news/metro/stories/MYSA010307.02A.richter.132c153.html Express-News staffer resigns after plagiarism in column is discovered] {{Webarchive|url=https://web.archive.org/web/20071015045010/http://www.mysanantonio.com/news/metro/stories/MYSA010307.02A.richter.132c153.html|date=2007-10-15}}", ''[[San Antonio Express-News]]'', [[January 9]], [[2007]].</ref><ref>"[http://starbulletin.com/2006/01/13/news/story03.html Inquiry prompts reporter's dismissal] {{Webarchive|url=https://web.archive.org/web/20080628204906/http://starbulletin.com/2006/01/13/news/story03.html|date=2008-06-28}}", ''[[Honolulu Star-Bulletin]]'', [[January 13]], [[2007]].</ref> In July 2007, Wikipedia was the focus of a 30-minute documentary on [[BBC Radio 4]]<ref>{{cite web|url=http://www.bbc.co.uk/radio4/factual/pip/efv21/|title=Radio 4 Documentary}}</ref> which argued that, with increased usage and awareness, the number of references to Wikipedia in popular culture is such that the term is one of a select band of 21st-century nouns that are so familiar ([[Google]], [[Facebook]], [[YouTube]]) that they no longer need explanation and are on a par with such 20th-century terms as [[The Hoover Company|Hoovering]] or [[Coca-Cola|Coke]]. Many parody Wikipedia's openness, with characters vandalizing or modifying the online encyclopedia project's articles. Notably, comedian [[Stephen Colbert]] has parodied or referenced Wikipedia on numerous episodes of his show ''[[The Colbert Report]]'' and coined the related term "[[wikiality]]".<ref name="wikiality">{{cite news|title=Wikiality|publisher=Comedycentral.com|url=http://www.comedycentral.com/motherload/index.jhtml?ml_video=72347|author=Stephen Colbert|date=[[2006-07-30]]|access-date=2022-09-03|archive-date=2008-03-08|archive-url=https://web.archive.org/web/20080308053730/http://www.comedycentral.com/motherload/index.jhtml?ml_video=72347|url-status=dead}} {{Webarchive|url=https://web.archive.org/web/20080308053730/http://www.comedycentral.com/motherload/index.jhtml?ml_video=72347 |date=2008-03-08 }}</ref> Wikipedia has also created an impact upon forms of media. Some media sources satirize Wikipedia's susceptibility to inserted inaccuracies, such as a front-page article in ''[[The Onion]]'' in July 2006 with the title "Wikipedia Celebrates 750 Years of American Independence",<ref>{{cite web |url=http://www.theonion.com/content/node/50902 |title=Wikipedia Celebrates 750 Years Of American Independence |accessmonthday=[[October 15]] |accessyear=2006 |year=2006 |work=[http://www.theonion.com/content/index The Onion] |access-date=2022-09-03 |archive-date=2011-08-23 |archive-url=https://web.archive.org/web/20110823075932/http://www.theonion.com/articles/wikipedia-celebrates-750-years-of-american-indepen%2C2007/ |url-status=dead }} {{Webarchive|url=https://web.archive.org/web/20110823075932/http://www.theonion.com/articles/wikipedia-celebrates-750-years-of-american-indepen%2C2007/ |date=2011-08-23 }}</ref> while others may draw upon Wikipedia's statement that anyone can edit, such as "[[The Negotiation]]", an episode of ''[[The Office (U.S. TV series)|The Office]]'', where character [[Michael Scott (The Office)|Michael Scott]] said that "Wikipedia is the best thing ever. Anyone in the world can write anything they want about any subject, so you know you are getting the best possible information", and a select few parody Wikipedia's policies, such as the ''xkcd'' strip named "Wikipedian Protester", that also included the joke "Semi-protect the Constitution!" The first documentary film about Wikipedia, entitled ''[[Truth in Numbers: The Wikipedia Story]]'', is scheduled for 2009 release. Shot on several continents, the film will cover the history of Wikipedia and feature interviews with Wikipedia editors around the world.<ref>{{cite web|url=http://wikidocumentary.wikia.com/wiki/Main_Page|title=wikidocumentary.wikia.com/wiki/Main_Page<!--INSERT TITLE-->|access-date=2022-09-03|archive-date=2010-01-11|archive-url=https://web.archive.org/web/20100111205115/http://wikidocumentary.wikia.com/wiki/Main_Page|url-status=dead}} {{Webarchive|url=https://web.archive.org/web/20100111205115/http://wikidocumentary.wikia.com/wiki/Main_Page |date=2010-01-11 }}</ref><ref>{{cite web| url=http://www.sfgate.com/cgi-bin/article.cgi?f=/c/a/2007/03/11/PKGRJN87UI1.DTL| title=Industry Buzz| last=Hart| first=Hugh| date=[[March 11]], [[2007]]| publisher=[[San Francisco Chronicle|SFGate.com]]| access-date=2022-09-03| archive-date=2012-05-15| archive-url=https://web.archive.org/web/20120515042942/http://www.sfgate.com/cgi-bin/article.cgi?f=%2Fc%2Fa%2F2007%2F03%2F11%2FPKGRJN87UI1.DTL| url-status=dead}}</ref> Dutch filmmaker [[Tegenlicht|IJsbrand van Veelen]] premiered his 45-minute documentary ''The Truth According to Wikipedia'' in April, 2008.<ref>{{cite web |url=http://www.techcrunch.com/2008/04/08/the-truth-according-to-wikipedia/ |title=The Truth According to Wikipedia |last=Schonfeld |first=Erick |date=[[April 8]], [[2008]] |publisher=TechCruch.com}}</ref> <!-- This paragraph doesn't make much sense; what is relevancy? He was merely using Wikipedia (as an example) to make a point. -- TakuyaMurata On [[September 28]], [[2007]], Italian politician [[Franco Grillini]] raised a parliamentary question with the Minister of Cultural Resources and Activities about the necessity of [[Panoramafreiheit|freedom of panorama]]. He said that the lack of such freedom forced Wikipedia, "the seventh most consulted website" to forbid all images of modern Italian buildings and art, and claimed this was hugely damaging to tourist revenues.<ref>{{cite web|url=http://www.grillini.it/show.php?4885|title=Comunicato stampa. On. Franco Grillini. Wikipedia. Interrogazione a Rutelli. Con "diritto di panorama" promuovere arte e architettura contemporanea italiana. Rivedere con urgenza legge copyright|date=[[12 October]] [[2007]]}}</ref> -->On [[September 16]], [[2007]], ''[[The Washington Post]]'' reported that Wikipedia had become a focal point in the 2008 election campaign, saying, "Type a candidate's name into Google, and among the first results is a Wikipedia page, making those entries arguably as important as any ad in defining a candidate. Already, the presidential entries are being edited, dissected and debated countless times each day."<ref>{{cite news |url=http://www.washingtonpost.com/wp-dyn/content/article/2007/09/16/AR2007091601699_pf.html |title=On Wikipedia, Debating 2008 Hopefuls' Every Facet |author=Jose Antonio Vargas |publisher=The Washington Post |date=2007-09-17}} </ref> An October 2007 [[Reuters]] article, entitled "Wikipedia page the latest status symbol", reported the recent phenomenon of how having a Wikipedia article vindicates one's notability.<ref>{{cite news |url=http://www.reuters.com/article/domesticNews/idUSN2232893820071022?sp=true |title=Wikipedia page the latest status symbol |author=Jennifer Ablan |publisher=Reuters |date=2007-10-22 |accessdate=2007-10-24}}</ref> Wikipedia won two major awards in May 2004.<ref>"[http://meta.wikimedia.org/wiki/Trophy_box Trophy Box]", [[Wikipedia:Meta|Meta-Wiki]] ([[March 28]], [[2005]]).</ref> The first was a Golden Nica for Digital Communities of the annual [[Prix Ars Electronica]] contest; this came with a €10,000 (£6,588; $12,700) grant and an invitation to present at the PAE Cyberarts Festival in [[Austria]] later that year. The second was a Judges' [[Webby Awards|Webby Award]] for the "community" category.<ref>{{cite web|url=http://www.webbyawards.com/webbys/winners-2004.php|title=Webby Awards 2004|publisher=The International Academy of Digital Arts and Sciences|date=2004|accessdate=2007-06-19|archive-date=2011-07-22|archive-url=https://web.archive.org/web/20110722174246/http://www.webbyawards.com/webbys/winners-2004.php|url-status=dead}} {{Webarchive|url=https://web.archive.org/web/20110722174246/http://www.webbyawards.com/webbys/winners-2004.php |date=2011-07-22 }}</ref> Wikipedia was also nominated for a "Best Practices" Webby. On [[January 26]], [[2007]], Wikipedia was also awarded the fourth highest brand ranking by the readers of brandchannel.com, receiving 15% of the votes in answer to the question "Which brand had the most impact on our lives in 2006?"<ref>{{cite news |first=Anthony |last=Zumpano |title=Similar Search Results: Google Wins |url=http://www.brandchannel.com/features_effect.asp?pf_id=352 |publisher=[[Interbrand]] |date=[[2007-01-29]] |accessdate=2007-01-28 |archive-date=2007-02-20 |archive-url=https://web.archive.org/web/20070220095907/http://brandchannel.com/features_effect.asp?pf_id=352 |url-status=dead }}</ref> ==Related projects== {{sisterlinks}} A number of interactive multimedia encyclopedias incorporating entries written by the public existed long before Wikipedia was founded. The first of these was the 1986 [[BBC Domesday Project]], which included text (entered on [[BBC Micro]] computers) and photographs from over 1&nbsp;million contributors in the [[United Kingdom|UK]], and covering the geography, art and culture of the UK. This was the first interactive multimedia encyclopedia (and was also the first major multimedia document connected through internal links), with the majority of articles being accessible through an interactive map of the UK. The user-interface and part of the content of the Domesday Project have now been emulated on a website.<ref name="Domesday Project">[http://www.domesday1986.com/ Web-based emulator of the Domesday Project User Interface] and data from the Community Disc (contributions from the general public) -- most articles can be accessed using the interactive map</ref> One of the most successful early online encyclopedias incorporating entries by the public was [[h2g2]], which was also created by the [[BBC]]. The h2g2 encyclopedia was relatively light-hearted, focusing on articles which were both witty and informative. Both of these projects had similarities with Wikipedia, but neither gave full editorial freedom to public users. Wikipedia has also spawned several sister projects. The first, "In Memoriam: September 11<!--Do not reformat this date, it is quoted--> Wiki",<ref>{{cite web|url=http://www.sep11memories.org/wiki/In_Memoriam|title=sep11memories.org/<!--INSERT TITLE-->|accessdate=2007-02-06|archive-date=2009-03-12|archive-url=https://web.archive.org/web/20090312042108/http://www.sep11memories.org/wiki/In_Memoriam}}</ref> created in October 2002,<ref>[http://www.sep11memories.org/index.php?title=In_Memoriam&oldid=1502 First edit to the wiki] {{Webarchive|url=https://web.archive.org/web/20110929203116/http://www.sep11memories.org/index.php?title=In_Memoriam&oldid=1502|date=2011-09-29}} In Memoriam: September 11 wiki ([[October 28]], [[2002]]),</ref> detailed the [[September 11, 2001 attacks]]; this project was closed in October 2006. [[Wiktionary]], a dictionary project, was launched in December 2002;<ref>"[http://meta.wikimedia.org/w/index.php?title=Wikimedia_News&diff=prev&oldid=4133 Announcement of Wiktionary's creation]", [[December 12]], [[2002]]. Retrieved on [[2007-02-02]].</ref> [[Wikiquote]], a collection of quotations, a week after Wikimedia launched, and [[Wikibooks]], a collection of collaboratively written free books. Wikimedia has since started a number of other projects, including [[Wikiversity]], a project for the creation of free learning materials and supporting learning activities.<ref name="OurProjects">"[http://wikimediafoundation.org/wiki/Our_projects Our projects]", [[Wikimedia Foundation]]. Retrieved on [[2007-01-24]]</ref> A similar non-wiki project, the [[GNUPedia]] project, co-existed with Nupedia early in its history; however, it has been retired and its creator, [[free software]] figure [[Richard Stallman]], has lent his support to Wikipedia.<ref name="stallman1999" /> Other websites centered on collaborative [[knowledge base]] development have drawn inspiration from or inspired Wikipedia. Some, such as [[Susning.nu]], [[Enciclopedia Libre]], and [[WikiZnanie]] likewise employ no formal review process, whereas others use more traditional [[peer review]], such as [[Encyclopedia of Life]], [[Stanford Encyclopedia of Philosophy]], [[Scholarpedia]], [[h2g2]] and [[Everything2]]. Jimmy Wales, the ''de facto'' leader of Wikipedia,<ref name="defactoleader">{{cite news |first=Holden |last=Frith |url=http://technology.timesonline.co.uk/tol/news/tech_and_web/the_web/article1571519.ece |title=Wikipedia founder launches rival online encyclopedia |publisher=''[[The Times]]'' |date=[[March 26]], [[2007]], |accessdate=2007-06-27 |quote=<small>Wikipedia's de facto leader, Jimmy Wales, stood by the site's format.</small> }} {{Webarchive|url=https://web.archive.org/web/20110427034628/http://technology.timesonline.co.uk/tol/news/tech_and_web/the_web/article1571519.ece |date=2011-04-27 }}<small> – Holden Frith.</small></ref> said in an interview in regard to the online encyclopedia [[Citizendium]] which is overviewed by experts in their respective fields:<ref name=Orlowski18> {{cite news |first=Andrew |last=Orlowski |authorlink=Andrew Orlowski |url=http://www.theregister.co.uk/2006/09/18/sanger_forks_wikipedia/ |title=Wikipedia founder forks Wikipedia, More experts, less fiddling? |publisher=''[[The Register]]'' |date=[[September 18]], [[2006]] |accessdate=2007-06-27 |quote=<small>Larry Sanger describes the Citizendium project as a "progressive or gradual fork", with the major difference that experts have the final say over edits.</small>}}<small> – Andrew Orlowski.</small></ref> "We welcome a diversity of efforts. If Larry's project is able to produce good work, we will benefit from it by copying it back into Wikipedia."<ref name="JayLyman">{{cite news |first=Jay |last=Lyman |url=http://www.crmbuyer.com/story/53137.html |title=Wikipedia Co-Founder Planning New Expert-Authored Site |publisher=LinuxInsider |date=[[September 20]], [[2006]] |accessdate=2007-06-27 |archive-date=2012-05-24 |archive-url=https://web.archive.org/web/20120524164124/http://www.crmbuyer.com/story/53137.html |url-status=dead }} {{Webarchive|url=https://web.archive.org/web/20120524164124/http://www.crmbuyer.com/story/53137.html |date=2012-05-24 }}</ref> ==See also== {{meta|List of Wikipedias}} *[[List of online encyclopedias]] * [[List of wikis]] * [[Open content]] * [[USA Congressional staff edits to Wikipedia]] * [[User-generated content]] * [[Recursion]] * {{srlink|Wikipedia:About}} * {{srlink|Wikipedia:Press coverage}} *[[List of websites named after Wikipedia]] ==Further reading== ===Press coverage=== *{{cite news |url=http://www.economist.com/science/tq/displaystory.cfm?story_id=11484062 |title=The free-knowledge fundamentalist |date=2008-06-05 |accessdate=2008-06-05 |publisher=The Economist}} *{{cite news |url=http://www.nytimes.com/2007/07/01/magazine/01WIKIPEDIA-t.html?_r=1&ref=magazine&oref=slogin |title=All the News That's Fit to Print Out |first=Jonathan |last=Dee |publisher=The New York Times Magazine |date=2007-07-01 |accessdate=2008-02-22}} *{{cite news |title=Wikipedia 2.0 - now with added trust |url=http://technology.newscientist.com/article/mg19526226.200-wikipedia-20-%C3%A2-now-with-added-trust.html |date=2007-09-20 |accessdate=2008-02-22 |first=Jim |last=Giles |publisher=New Scientist |archive-date=2008-02-23 |archive-url=https://web.archive.org/web/20080223092137/http://technology.newscientist.com/article/mg19526226.200-wikipedia-20-%C3%A2-now-with-added-trust.html |url-status=dead }} *{{cite news |title=Wikipedia Rules |url=http://thephoenix.com/article_ektid52864.aspx |publisher=[[The Phoenix (newspaper)|The Phoenix]] |date=2007-12-02 |accessdate=2008-02-22 |first=Mike |last=Miliard}} *{{cite news |url=http://www.time.com/time/magazine/article/0,9171,1066904-1,00.html |title=It's a Wiki, Wiki World |first=Chris |last=Taylor |date=2005-05-29 |publisher=Time |accessdate=2008-02-22 |archive-date=2009-10-29 |archive-url=https://web.archive.org/web/20091029161646/http://www.time.com/time/magazine/article/0,9171,1066904-1,00.html |url-status=dead }} {{Webarchive|url=https://web.archive.org/web/20091029161646/http://www.time.com/time/magazine/article/0,9171,1066904-1,00.html |date=2009-10-29 }} *{{cite news |url=http://www.theatlantic.com/doc/200609/wikipedia |title=The Hive |first=Marshall |last=Poe |authorlink=Marshall Poe |date=2006-09 |accessdate=2008-03-22 |publisher=[[The Atlantic Monthly]]}} *Balke, Jeff. [http://blogs.chron.com/brokenrecord/2008/03/for_music_fans_wikipedia_myspa.html For Music Fans: Wikipedia > MySpace] {{Webarchive|url=https://web.archive.org/web/20090907093533/http://blogs.chron.com/brokenrecord/2008/03/for_music_fans_wikipedia_myspa.html |date=2009-09-07 }} ''[[Houston Chronicle]]'' ===Academic studies=== * {{cite journal|author=Ulrike Pfeil, Panayiotis Zaphiris, and Chee Siang Ang|date=2006|title=Cultural differences in collaborative authoring of Wikipedia|journal=Journal of Computer-Mediated Communication|volume=12|issue=1|url=http://jcmc.indiana.edu./vol12/issue1/pfeil.html|access-date=2021-02-09|archive-date=2009-12-01|archive-url=https://web.archive.org/web/20091201202646/http://jcmc.indiana.edu/vol12/issue1/pfeil.html|url-status=dead}} * {{cite web|title=Do as I do: leadership in the Wikipedia|author=Joseph M. Reagle Jr.|url=http://reagle.org./joseph/2005/ethno/leadership.html|work=Wikipedia Drafts|date=2005}} * {{cite journal |url=http://www.firstmonday.org/issues/issue12_4/wilkinson/index.html |title=Assessing the value of cooperation in Wikipedia |date=April 2007 |first=Dennis M. |last=Wilkinson |co-author=Bernardo A. Huberman |journal=First Monday |volume=12 |issue=4 |accessdate=2008-02-22 |archive-date=2011-04-30 |archive-url=https://web.archive.org/web/20110430033015/http://firstmonday.org/issues/issue12_4/wilkinson/index.html |url-status=dead }} * {{cite journal |url=http://www.firstmonday.org/issues/issue12_8/nielsen/index.html |title=Scientific citations in Wikipedia |date=August 2007 |journal=First Monday |volume=12 |issue=8 |accessdate=2008-02-22 |first=Finn Årup |last=Nielsen |archive-date=2008-12-04 |archive-url=https://web.archive.org/web/20081204114114/http://www.firstmonday.org/issues/issue12_8/nielsen/index.html |url-status=dead }} ===Essays=== *[[Roy Rosenzweig]]: [http://chnm.gmu.edu/resources/essays/d/42 Can History be Open Source? Wikipedia and the Future of the Past]. (Originally published in [[The Journal of American History]] Volume 93, Number 1, June 2006, p117-46) *[http://www.nybooks.com/articles/21131 The Charms of Wikipedia] [[Nicholson Baker]] article on Wikipedia from ''[[The New York Review of Books]]'' ==References== {{reflist|2}} ==External links== {{Spoken Wikipedia|Wikipedia.ogg|2005-06-25}} *[http://www.wikipedia.org/ Wikipedia] – multilingual portal (contains links to all language editions of the project) *[http://www.dmoz.org/Computers/Open_Source/Open_Content/Encyclopedias/Wikipedia/ Wikipedia] {{Webarchive|url=https://web.archive.org/web/20091203014943/http://www.dmoz.org/Computers/Open_Source/Open_Content/Encyclopedias/Wikipedia/ |date=2009-12-03 }} at the [[Open Directory Project]] *[http://www.cbc.ca/news/background/tech/wikipedia.html CBC News: I, editor] *[http://www.wikihow.com/Contribute-to-Wikipedia Help Edit Wikipedia] A [[wikiHow]] article. *[http://www.cnn.com/2007/TECH/11/01/wikipedia.assignment.ap/index.html Class assignment: Write an original Wikipedia article] *[irc://irc.freenode.net/wikipedia #wikipedia] on [[freenode]] {{Wikipedia}} {{Wikimedia Foundation}} {{Wikipedias}} [[Category:Web 2.0]] [[Category:Wikipedia]] [[Category:Free encyclopedias]] [[Category:Online encyclopedias]] [[Category:General encyclopedias]] [[Category:Wikis]] [[Category:Internet properties established in 2001]] [[Category:Wikimedia projects]] [[Category:Advertising-free websites]] [[Category:Social Information Processing]] {{Link FA|ceb}} [[aa:Wikipedia]] [[af:Wikipedia]] [[als:Wikipedia]] [[am:ውክፔዲያ]] [[ang:Wicipǣdia]] [[ar:ويكيبيديا]] [[an:Biquipedia]] [[arc:ܘܝܟܝܦܕܝܐ]] [[roa-rup:Wikipedia]] [[frp:Vouiquipèdia]] [[ast:Uiquipedia]] [[gn:Vikipetã]] [[ay:Wikipidiya]] [[az:Vikipediya]] [[bm:Wikipedia]] [[bn:উইকিপিডিয়া]] [[zh-min-nan:Wikipedia]] [[map-bms:Wikipedia]] [[ba:Wikipedia]] [[be:Вікіпедыя]] [[be-x-old:Вікіпэдыя]] [[bar:Wikipedia]] [[bs:Wikipedia]] [[br:Wikipedia]] [[bg:Уикипедия]] [[ca:Viquipèdia]] [[cv:Википеди]] [[ceb:Wikipedya]] [[cs:Wikipedie]] [[ch:Wikipedia]] [[co:Wikipedia]] [[cy:Wicipedia]] [[da:Wikipedia]] [[de:Wikipedia]] [[nv:Wikiibíídiiya]] [[dsb:Wikipedija]] [[et:Vikipeedia]] [[el:Βικιπαίδεια]] [[myv:Википедиясь]] [[es:Wikipedia]] [[eo:Vikipedio]] [[ext:Wikipédia]] [[eu:Wikipedia]] [[fa:ویکی‌پدیا]] [[fo:Wikipedia]] [[fr:Wikipédia]] [[fy:Wikipedy]] [[ff:Wikipeediya]] [[fur:Vichipedie]] [[ga:Vicipéid]] [[gan:維基百科]] [[gv:Wikipedia]] [[gd:Bhicipèidia]] [[gl:Wikipedia]] [[gu:વિકિપીડિયા]] [[zh-classical:維基大典]] [[hak:Wikipedia]] [[ko:위키백과]] [[hy:Վիքիփեդիա]] [[hi:विकिपीडिया]] [[hsb:Wikipedija]] [[hr:Wikipedija]] [[io:Wikipedio]] [[ig:Wikipedia]] [[ilo:Wikipedia]] [[bpy:উইকিপিডিয়া]] [[id:Wikipedia]] [[ia:Wikipedia]] [[iu:ᐅᐃᑭᐱᑎᐊ/uikipitia]] [[ik:Wikipedia]] [[os:Википеди]] [[is:Wikipedia]] [[it:Wikipedia]] [[he:ויקיפדיה]] [[jv:Wikipedia]] [[kl:Wikipedia]] [[kn:ವಿಕಿಪೀಡಿಯ]] [[ka:ვიკიპედია]] [[csb:Wikipedijô]] [[kk:Уикипедия]] [[kw:Wikipedya]] [[sw:Wikipedia]] [[ht:Wikipedya]] [[ku:Wikipedia]] [[lad:ויקיפידיה]] [[lbe:Википедия]] [[lo:ວິກິພີເດຍ]] [[la:Wikipedia]] [[lv:Vikipēdija]] [[lb:Wikipedia]] [[lt:Vikipedija]] [[lij:Wikipedia]] [[li:Wikipedia]] [[ln:Wikipedia]] [[jbo:uikipedias]] [[lmo:Wikipedia]] [[hu:Wikipédia]] [[mk:Википедија]] [[mg:Wikipedia]] [[ml:വിക്കിപീഡിയ]] [[mt:Wikipedija]] [[mi:Wikipedia]] [[mr:विकिपीडिया]] [[mzn:ویکیپدیا]] [[ms:Wikipedia]] [[cdo:Wikipedia]] [[mdf:Википедиесь]] [[mn:Википедиа]] [[nah:Huiquipedia]] [[na:Wikipedia]] [[nl:Wikipedia]] [[nds-nl:Wikipedia]] [[ne:विकिपीडिया]] [[ja:ウィキペディア]] [[nap:Wikipedia]] [[no:Wikipedia]] [[nn:Wikipedia]] [[nrm:Viqùipédie]] [[oc:Wikipèdia]] [[ng:Wikipedia]] [[ug:Wikipédiye]] [[uz:Vikipediya]] [[pa:ਵਿਕਿਪੀਡਿਆ]] [[pag:Wikipedia]] [[pap:Wikipedia]] [[ps:پروژه:په هکله]] [[km:វិគីភីឌា]] [[pms:Wikipedia]] [[nds:Wikipedia]] [[pl:Wikipedia]] [[pt:Wikipédia]] [[kaa:Wikipedia]] [[crh:Vikipediya]] [[ksh:Wikkipedija]] [[ro:Wikipedia]] [[rmy:Vikipidiya]] [[rm:Vichipedia]] [[qu:Wikipidiya]] [[ru:Википедия]] [[se:Wikipediija]] [[sc:Wikipedia]] [[sco:Wikipaedia]] [[sq:Wikipedia]] [[scn:Wikipedia]] [[si:විකිපීඩියා]] [[simple:Wikipedia]] [[sd:وڪيپيڊيا]] [[sk:Wikipédia]] [[cu:Википє́дїꙗ]] [[sl:Wikipedija]] [[szl:Wikipedyjo]] [[so:Wikipedia]] [[sr:Википедија]] [[sh:Wikipedia]] [[su:Wikipédia]] [[fi:Wikipedia]] [[sv:Wikipedia]] [[tl:Wikipedia]] [[ta:விக்கிப்பீடியா]] [[kab:Wikipedia]] [[roa-tara:Uicchipèdie]] [[tt:Wikipedia]] [[te:వికీపీడియా]] [[tet:Wikipédia]] [[th:วิกิพีเดีย]] [[vi:Wikipedia]] [[tg:Википедиа]] [[tpi:Wikipedia]] [[chr:ᏫᎩᏇᏗᏯ]] [[chy:Wikipedia]] [[tr:Vikipedi]] [[tk:Wikipediýa]] [[udm:Википедия]] [[bug:Wikipedia]] [[uk:Вікіпедія]] [[vec:Wikipedia]] [[fiu-vro:Vikipeediä]] [[wa:Wikipedia]] [[vls:Wikipedia]] [[war:Wikipedia]] [[wo:Wikipedia]] [[wuu:维基百科]] [[yi:‫װיקיפעדיע]] [[yo:Wikipedia]] [[zh-yue:維基百科]] [[diq:Wikipediya]] [[zea:Wikipedia]] [[bat-smg:Vikipedėjė]] [[zh:维基百科]] dgt0vxnu08tyk8rvb8kduxavn095e7d Pentácoro 0 119355 739791 470014 2026-04-29T18:39:02Z ~2026-26264-39 73756 Replaced content with "**" 739791 wikitext text/x-wiki ** lzcv8ldtut881bgv0zfmqok6a5pc0fm 739792 739791 2026-04-29T18:39:05Z AutoModeratorTest 61468 Reverted edit by [[Special:Contributions/~2026-26264-39|~2026-26264-39]] ([[User talk:~2026-26264-39|talk]]) to last revision by [[User:Asmcds1995|Asmcds1995]] 470014 wikitext text/x-wiki == Referências == * T. Gosset : ''On the Regular and Semi-Regular Figures in Space of n Dimensions'', Messenger of Mathematics, Macmillan, 1900 * [[Harold Scott MacDonald Coxeter|HSM Coxeter]] : ** {{Cite book|last=Coxeter|first=H.S.M.|author-link=Harold Scott MacDonald Coxeter|year=1973|title=Regular Polytopes|publisher=Dover|place=New York|edition=3rd|title-link=Regular Polytopes (book)}} *** *** ** {{Citation|last=Coxeter|first=H.S.M.|author-link=Harold Scott MacDonald Coxeter|year=1991|title=Regular Complex Polytopes|place=Cambridge|publisher=Cambridge University Press|edition=2nd}} *** *** *** * [[John Conway|John H. Conway]], Heidi Burgiel, Chaim Goodman-Strass, ''The Symmetries of Things'' 2008,{{ISBN|978-1-56881-220-5}} [[ISBN (identifier)|ISBN]]&nbsp;[[Special:BookSources/978-1-56881-220-5|978-1-56881-220-5]] (Capítulo 26. pp.&nbsp;409: Hemicubos: 1 <sub>n1</sub> ) * [[Norman Johnson (matemático)|Norman Johnson]] ''Uniform Polytopes'', Manuscript (1991) ** NW Johnson: ''The Theory of Uniform Polytopes and Honeycombs'', Ph.D. (1966) 4p7ry7jv4rkw3ewlbmq312bngssczqj Carlo Reguzzoni 0 122000 739779 681223 2026-04-29T15:03:52Z NWBibBot 72927 Bot: I tried to modify 739779 wikitext text/x-wiki {{short description|Italian footballer}} {{refimprove|date=May 2009}} {{Infobox football biography | name = Carlo Reguzzoni | image = | fullname = | birth_date = {{birth date|df=yes|1908|1|18}} | birth_place = [[Busto Arsizio]], [[Kingdom of Italy]] | death_date = {{death date and age|df=yes|1996|12|16|1908|1|18}} | death_place = Busto Arsizio, [[Italy]] | height = | position = [[Forward (association football)|Forward]] | youthyears1 = | youthclubs1 = | years1 = 1927–1930 | years2 = 1930–1943 | years3 = 1944 | years4 = 1945–1946 | years5 = 1946–1948 | clubs1 = [[Aurora Pro Patria 1919|Pro Patria]] | clubs2 = [[Bologna F.C. 1909|Bologna]] | clubs3 = Pro Patria | clubs4 = Bologna | clubs5 = Pro Patria | caps1 = 75 | caps2 = 359 | caps3 = 14 | caps4 = 18 | caps5 = 51 | goals1 = 52 | goals2 = 141 | goals3 = 3 | goals4 = 2 | goals5 = 14 | nationalyears1 = 1940 | nationalteam1 = [[Italy national football team|Italy]] | nationalcaps1 = 1 | nationalgoals1 = 0 }} [[File:1938–39 Bologna Associazione Giuoco del Calcio.jpg|thumb|testing]] '''Carlo Reguzzoni''' ({{IPA-it|ˈkarlo reɡutˈtsoːni}}; 18 January 1908 – 16 December 1996) was an [[Italians|Italian]] [[Association football|footballer]] who played as a [[Forward (association football)#Winger|winger]]. ==Club career== Reguzzoni was born in [[Busto Arsizio]], in the [[Varese Province|province of Varese]], [[Lombardy]]. He made his [[Serie A]] debut with [[Aurora Pro Patria 1919|Pro Patria]] on 6 October 1929, in a 4–2 home win over [[U.S. Cremonese|Cremonese]]. He also played with [[Bologna F.C. 1909|Bologna]] in the 1930s and 1940s, where he spent most of his career, scoring 143 goals in 377 games for the club, making him Bologna's second highest goalscorer of all-time, behind only [[Angelo Schiavio]].<ref name="enciclopediadelcalcio.it"/><ref name="signore">{{cite web|url=http://www.gazzetta.it/Calcio/Serie-A/Bologna/04-01-2017/addio-pascutti-bologna-piange-signore-gol-scudetto-180285209336.shtml|title=È morto Ezio Pascutti, Bologna piange il signore del gol|publisher=La Gazzetta dello Sport|language=Italian|date=4 January 2017|accessdate=4 January 2017}}</ref> In total, he scored 155 goals in 401 appearances in [[Serie A]].<ref name="enciclopediadelcalcio.it">{{cite web|url=http://www.enciclopediadelcalcio.it/Reguzzoni.html|title=Reguzzoni, Carlo|publisher=EnciclopediaDelCalcio.it|language=Italian|accessdate=5 January 2017|archive-date=4 March 2016|archive-url=https://web.archive.org/web/20160304123139/http://www.enciclopediadelcalcio.it/Reguzzoni.html|url-status=dead}}</ref> ==International career== Reguzzoni made his only appearance for the [[Italy national football team]] on 14 April, 1940, in a 2–1 home win over [[Romania national football team|Romania]].<ref name="enciclopediadelcalcio.it"/> ==Honours== ===Club=== ;Bologna *[[Serie A]]: [[1935–36 Serie A|1935–36]], [[1936–37 Serie A|1936–37]], [[1938–39 Serie A|1938–39]], [[1940–41 Serie A|1940–41]] ==References== {{reflist}} {{DEFAULTSORT:Reguzzoni, Carlo}} {{Italy-footy-forward-1900s-stub}} == Weblinks == {{NWBIB}} [[Category:1908 births]] [[Category:1996 deaths]] [[Category:People from Busto Arsizio]] [[Category:Italian footballers]] [[Category:Italy international footballers]] [[Category:Bologna F.C. 1909 players]] [[Category:Serie A players]] [[Category:Serie B players]] [[Category:Association football forwards]] [[Category:Footballers from Lombardy]] 012ypb4dv1r0lnod9n4drz8jleh831a User:SongVĩ.Bot II 2 124239 739784 739707 2026-04-29T17:00:16Z SongVĩ.Bot II 52414 [[User:SongVĩ.Bot II|Task 0]]: Đã 1584 ngày... 739784 wikitext text/x-wiki Cập nhật lần cuối: 30-04-2026 Đã 1584 ngày... m6jwo9l2im7zlow10qphtuadlzeg0xn Test 0 155073 739780 739762 2026-04-29T15:06:28Z NWBibBot 72927 739780 wikitext text/x-wiki {{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Hostedde | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.566 | Längengrad = 7.537 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 80 <!--www.tim-online.nrw.de--> | Höhe-Bezug = NHN | Fläche = 4.316 | Einwohner = 2345 | Einwohner-Stand-Datum = 2024-12-31 | Einwohner-Quelle = <ref> [https://www.dortmund.de/dortmund/projekte/rathaus/verwaltung/dortmunder-statistik/downloads/02_02_bevoelkerung_nach_geschlecht_und_staatsangehoerigkeit_in_den_statistischen_bezirken_.pdf Bevölkerungszahlen in den statistischen Bezirken am 31.12.2024 (im 5er-Rundungsverfahren)] (PDF; 135&nbsp;kB)</ref> | Eingemeindungsdatum = 1922-11-22 | Eingemeindet-nach = [[Derne|Altenderne-Oberbecker]] | Postleitzahl1 = 44329 | Postleitzahl2 = | Vorwahl1 = 0231 {{!}}- class="hintergrundfarbe1" {{!}} [[Statistischer Bezirk]]: {{!}} 22 | Lagekarte = Dortmund Statistischer Bezirk Hostedde.svg | Lagekarte-Beschreibung = }} [[Datei:Hostedde.jpg|mini|190px|Östlicher Ortseingang von Hostedde mit Hostedder Straße]] '''Hostedde''' ist ein Dortmunder Stadtteil im [[Stadtbezirk Scharnhorst]]. Der Stadtteil grenzt im Osten an [[Grevel]], im Süden an [[Alt-Scharnhorst]] und im Westen an [[Derne]]. Hostedde ist ein reines Wohngebiet. Es gibt keine Industrien und nur einige wenige kleinere Dienstleistungsunternehmen und Gastwirtschaften. Die quer durch Hostedde führende Hostedder Straße ist eine wichtige Verbindungsstraße zwischen [[Lanstrop]] und der Dortmunder Innenstadt. == Geschichte == Hostedde wurde erstmals 1280 als ''Hofstede'' urkundlich erwähnt. 1359 ''cum bonis in Hostaden'' in einer Urkunde genannt. Anfang des 15. Jahrhunderts wurde der Ort ''tho Hoffstaden'' im [[Volmestein|Volmarsteiner]] [[Lehnswesen|Lehnsregister]] geführt. Ab 1654 erscheint erstmals der heutige Ortsname Hostedde. Hostedde gehörte im [[Spätmittelalter]] und der [[Frühe Neuzeit|Frühen Neuzeit]] in eigener [[Bauerschaft]] (''Hoeffstede'') im Niederamt [[Bochum]] (historisch) zur [[Grafschaft Mark]]. Laut dem [[Schatzbuch der Grafschaft Mark]] von 1486 hatten in der Bauerschaft 5 Steuerpflichtige Hofbesitzer zwischen 2 und 6 [[Rheinischer Gulden|Goldgulden]] an Abgabe zu leisten.<ref>[[Aloys Meister]]: ''Die Grafschaft Mark'', Festschrift zum Gedächtnis der 300-jährigen Vereinigung mit Brandenburg-Preußen. 2. Band, Dortmund 1909, S.&nbsp;11 – Auszug aus dem Schatzbuch der Grafschaft Mark von 1486 (Bauerschaft Hostedde)</ref> Die Deutung des Ortsnamens kann mit ''Hofstätte'' umschrieben werden.<ref>Michael Flöer: ''Die Ortsnamen der Stadt Dortmund und der Stadt Hagen'', in: Westfälisches Ortsnamenbuch, Band 16, Bielefeld 2021, S.&nbsp;143/144</ref> Im 19. Jahrhundert war Hostedde eine Landgemeinde im [[Landkreis Dortmund]] und [[Amt Brackel]]. 1885 hatte die Gemeinde (plus 3 [[Wohnplatz|Wohnplätze]]) eine Fläche von 1,97 km², davon 112 [[Hektar|ha]] Ackerland, 24 ha Wiesen und 16 ha Holzungen. Es gab 26 Wohngebäude mit 40 Haushaltungen und 262 Einwohner.<ref>Gemeindelexikon für die Provinz Westfalen. Berlin 1887, S.&nbsp;80/81, Online-Ausgabe Münster, Universitäts- und Landesbibliothek 2014</ref> Am 22. November 1922 wurde Hostedde in die Gemeinde Altenderne-Oberbecker eingemeindet, die bereits am 27. Oktober 1923 in Derne umbenannt wurde. Am 1.&nbsp;April 1928 wurde der Ort zusammen mit Derne in die Stadt Dortmund eingegliedert.<ref>{{BibISBN|3402058758|Seite=226, 250}}</ref> Zum statistischen Bezirk Hostedde gehört neben dem Ortsteil Hostedde auch der östlich angrenzende Ortsteil [[Grevel]]. == Bevölkerung == Zum 31. Dezember 2024 lebten 2.345 Einwohner in Hostedde (mit [[Grevel]]). Struktur der Hostedder Bevölkerung (mit [[Grevel]]): * Bevölkerungsanteil der unter 18-Jährigen: 15,1 % [Dortmunder Durchschnitt: 16,2 % (2018)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik/pdf_statistik/veroeffentlichungen/statistikatlas/215_-_Statistikatlas_-_2019.pdf |wayback=20210626094819 |text=Bevölkerungsanteil der unter 18-Jährigen Statistikatlas 2019 |archiv-bot=2025-06-28 13:06:01 InternetArchiveBot }} (PDF; 9,1&nbsp;MB)</ref> * Bevölkerungsanteil der mindestens 65-Jährigen: 22,1 % [Dortmunder Durchschnitt: 20,2 % (2018)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik/pdf_statistik/veroeffentlichungen/statistikatlas/215_-_Statistikatlas_-_2019.pdf |wayback=20210626094819 |text=Bevölkerungsanteil der mindestens 65-Jährigen Statistikatlas 2019 |archiv-bot=2025-06-28 13:06:01 InternetArchiveBot }} (PDF; 9,1&nbsp;MB)</ref> * Ausländeranteil: 11,3 % [Dortmunder Durchschnitt: 22,3 % (2024)]<ref>[https://www.dortmund.de/dortmund/projekte/rathaus/verwaltung/dortmunder-statistik/downloads/02_02_bevoelkerung_nach_geschlecht_und_staatsangehoerigkeit_in_den_statistischen_bezirken_.pdf Staatsangehörigkeiten in den statistischen Bezirken am 31. Dezember 2024] (PDF; 135&nbsp;kB)</ref> * Arbeitslosenquote: 7,1 % [Dortmunder Durchschnitt: 11,0 % (2017)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik_3/statistik/wirtschaft_1/06_12_Arbeitslosenquoten_Statistische_Bezirke.pdf |wayback=20180625161337 |text=Arbeitslosenquoten nach statistischen Bezirken am 30. Juni 2017 |archiv-bot=2019-09-11 00:51:25 InternetArchiveBot }} (PDF-Datei)</ref> Das durchschnittliche Einkommen in Hostedde liegt etwa 10 % unter dem Dortmunder Durchschnitt. === Bevölkerungsentwicklung === {| class="wikitable" style="text-align:center" |- class="hintergrundfarbe5" ! style="text-align:left;" | '''Jahr''' || 1987 || 2003 || 2008 || 2013 || 2016 || 2019 !2022 |- | style="text-align:left;" | '''Einwohner''' || 2279 || 2330 || 2214 || 2179 || 2265 || 2332 |2326 |} == Einzelnachweise == <references /> {{Navigationsleiste Stadtteile von Dortmund}} [[Kategorie:Statistischer Bezirk von Dortmund]] [[Kategorie:Ehemalige Gemeinde (Dortmund)]] [[Kategorie:Gemeindeauflösung 1922]] 92zyb7mu2o7t5fwa0blkrq26uhg4etv 739781 739780 2026-04-29T15:07:09Z NWBibBot 72927 Bot: I tried to modify 739781 wikitext text/x-wiki {{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Hostedde | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.566 | Längengrad = 7.537 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 80 <!--www.tim-online.nrw.de--> | Höhe-Bezug = NHN | Fläche = 4.316 | Einwohner = 2345 | Einwohner-Stand-Datum = 2024-12-31 | Einwohner-Quelle = <ref> [https://www.dortmund.de/dortmund/projekte/rathaus/verwaltung/dortmunder-statistik/downloads/02_02_bevoelkerung_nach_geschlecht_und_staatsangehoerigkeit_in_den_statistischen_bezirken_.pdf Bevölkerungszahlen in den statistischen Bezirken am 31.12.2024 (im 5er-Rundungsverfahren)] (PDF; 135&nbsp;kB)</ref> | Eingemeindungsdatum = 1922-11-22 | Eingemeindet-nach = [[Derne|Altenderne-Oberbecker]] | Postleitzahl1 = 44329 | Postleitzahl2 = | Vorwahl1 = 0231 {{!}}- class="hintergrundfarbe1" {{!}} [[Statistischer Bezirk]]: {{!}} 22 | Lagekarte = Dortmund Statistischer Bezirk Hostedde.svg | Lagekarte-Beschreibung = }} [[Datei:Hostedde.jpg|mini|190px|Östlicher Ortseingang von Hostedde mit Hostedder Straße]] '''Hostedde''' ist ein Dortmunder Stadtteil im [[Stadtbezirk Scharnhorst]]. Der Stadtteil grenzt im Osten an [[Grevel]], im Süden an [[Alt-Scharnhorst]] und im Westen an [[Derne]]. Hostedde ist ein reines Wohngebiet. Es gibt keine Industrien und nur einige wenige kleinere Dienstleistungsunternehmen und Gastwirtschaften. Die quer durch Hostedde führende Hostedder Straße ist eine wichtige Verbindungsstraße zwischen [[Lanstrop]] und der Dortmunder Innenstadt. == Geschichte == Hostedde wurde erstmals 1280 als ''Hofstede'' urkundlich erwähnt. 1359 ''cum bonis in Hostaden'' in einer Urkunde genannt. Anfang des 15. Jahrhunderts wurde der Ort ''tho Hoffstaden'' im [[Volmestein|Volmarsteiner]] [[Lehnswesen|Lehnsregister]] geführt. Ab 1654 erscheint erstmals der heutige Ortsname Hostedde. Hostedde gehörte im [[Spätmittelalter]] und der [[Frühe Neuzeit|Frühen Neuzeit]] in eigener [[Bauerschaft]] (''Hoeffstede'') im Niederamt [[Bochum]] (historisch) zur [[Grafschaft Mark]]. Laut dem [[Schatzbuch der Grafschaft Mark]] von 1486 hatten in der Bauerschaft 5 Steuerpflichtige Hofbesitzer zwischen 2 und 6 [[Rheinischer Gulden|Goldgulden]] an Abgabe zu leisten.<ref>[[Aloys Meister]]: ''Die Grafschaft Mark'', Festschrift zum Gedächtnis der 300-jährigen Vereinigung mit Brandenburg-Preußen. 2. Band, Dortmund 1909, S.&nbsp;11 – Auszug aus dem Schatzbuch der Grafschaft Mark von 1486 (Bauerschaft Hostedde)</ref> Die Deutung des Ortsnamens kann mit ''Hofstätte'' umschrieben werden.<ref>Michael Flöer: ''Die Ortsnamen der Stadt Dortmund und der Stadt Hagen'', in: Westfälisches Ortsnamenbuch, Band 16, Bielefeld 2021, S.&nbsp;143/144</ref> Im 19. Jahrhundert war Hostedde eine Landgemeinde im [[Landkreis Dortmund]] und [[Amt Brackel]]. 1885 hatte die Gemeinde (plus 3 [[Wohnplatz|Wohnplätze]]) eine Fläche von 1,97 km², davon 112 [[Hektar|ha]] Ackerland, 24 ha Wiesen und 16 ha Holzungen. Es gab 26 Wohngebäude mit 40 Haushaltungen und 262 Einwohner.<ref>Gemeindelexikon für die Provinz Westfalen. Berlin 1887, S.&nbsp;80/81, Online-Ausgabe Münster, Universitäts- und Landesbibliothek 2014</ref> Am 22. November 1922 wurde Hostedde in die Gemeinde Altenderne-Oberbecker eingemeindet, die bereits am 27. Oktober 1923 in Derne umbenannt wurde. Am 1.&nbsp;April 1928 wurde der Ort zusammen mit Derne in die Stadt Dortmund eingegliedert.<ref>{{BibISBN|3402058758|Seite=226, 250}}</ref> Zum statistischen Bezirk Hostedde gehört neben dem Ortsteil Hostedde auch der östlich angrenzende Ortsteil [[Grevel]]. == Bevölkerung == Zum 31. Dezember 2024 lebten 2.345 Einwohner in Hostedde (mit [[Grevel]]). Struktur der Hostedder Bevölkerung (mit [[Grevel]]): * Bevölkerungsanteil der unter 18-Jährigen: 15,1 % [Dortmunder Durchschnitt: 16,2 % (2018)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik/pdf_statistik/veroeffentlichungen/statistikatlas/215_-_Statistikatlas_-_2019.pdf |wayback=20210626094819 |text=Bevölkerungsanteil der unter 18-Jährigen Statistikatlas 2019 |archiv-bot=2025-06-28 13:06:01 InternetArchiveBot }} (PDF; 9,1&nbsp;MB)</ref> * Bevölkerungsanteil der mindestens 65-Jährigen: 22,1 % [Dortmunder Durchschnitt: 20,2 % (2018)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik/pdf_statistik/veroeffentlichungen/statistikatlas/215_-_Statistikatlas_-_2019.pdf |wayback=20210626094819 |text=Bevölkerungsanteil der mindestens 65-Jährigen Statistikatlas 2019 |archiv-bot=2025-06-28 13:06:01 InternetArchiveBot }} (PDF; 9,1&nbsp;MB)</ref> * Ausländeranteil: 11,3 % [Dortmunder Durchschnitt: 22,3 % (2024)]<ref>[https://www.dortmund.de/dortmund/projekte/rathaus/verwaltung/dortmunder-statistik/downloads/02_02_bevoelkerung_nach_geschlecht_und_staatsangehoerigkeit_in_den_statistischen_bezirken_.pdf Staatsangehörigkeiten in den statistischen Bezirken am 31. Dezember 2024] (PDF; 135&nbsp;kB)</ref> * Arbeitslosenquote: 7,1 % [Dortmunder Durchschnitt: 11,0 % (2017)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik_3/statistik/wirtschaft_1/06_12_Arbeitslosenquoten_Statistische_Bezirke.pdf |wayback=20180625161337 |text=Arbeitslosenquoten nach statistischen Bezirken am 30. Juni 2017 |archiv-bot=2019-09-11 00:51:25 InternetArchiveBot }} (PDF-Datei)</ref> Das durchschnittliche Einkommen in Hostedde liegt etwa 10 % unter dem Dortmunder Durchschnitt. === Bevölkerungsentwicklung === {| class="wikitable" style="text-align:center" |- class="hintergrundfarbe5" ! style="text-align:left;" | '''Jahr''' || 1987 || 2003 || 2008 || 2013 || 2016 || 2019 !2022 |- | style="text-align:left;" | '''Einwohner''' || 2279 || 2330 || 2214 || 2179 || 2265 || 2332 |2326 |} == Einzelnachweise == <references /> {{Navigationsleiste Stadtteile von Dortmund}} [[Kategorie:Statistischer Bezirk von Dortmund]] [[Kategorie:Ehemalige Gemeinde (Dortmund)]] [[Kategorie:Gemeindeauflösung 1922]] == Weblinks == {{NWBIB}} spu2c47u6emq0xg0v5zd3u3evh43oju 739867 739781 2026-04-30T10:53:45Z ~2026-25195-23 73696 /* */ test 739867 wikitext text/x-wiki {{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Hostedde | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.566 | Längengrad = 7.537 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 80 <!--www.tim-online.nrw.de--> | Höhe-Bezug = NHN | Fläche = 4.316 | Einwohner = 2345 | Einwohner-Stand-Datum = 2024-12-31 | Einwohner-Quelle = <ref> [https://www.dortmund.de/dortmund/projekte/rathaus/verwaltung/dortmunder-statistik/downloads/02_02_bevoelkerung_nach_geschlecht_und_staatsangehoerigkeit_in_den_statistischen_bezirken_.pdf Bevölkerungszahlen in den statistischen Bezirken am 31.12.2024 (im 5er-Rundungsverfahren)] (PDF; 135&nbsp;kB)</ref> | Eingemeindungsdatum = 1922-11-22 | Eingemeindet-nach = [[Derne|Altenderne-Oberbecker]] | Postleitzahl1 = 44329 | Postleitzahl2 = | Vorwahl1 = 0231 {{!}}- class="hintergrundfarbe1" {{!}} [[Statistischer Bezirk]]: {{!}} 22 | Lagekarte = Dortmund Statistischer Bezirk Hostedde.svg | Lagekarte-Beschreibung = }} [[Datei:Hostedde.jpg|mini|190px|Östlicher Ortseingang von Hostedde mit Hostedder Straße]] '''Hostedde''' ist ein Dortmunder Stadtteil im [[Stadtbezirk Scharnhorst]]. Der Stadtteil grenzt im Osten an [[Grevel]], im Süden an [[Alt-Scharnhorst]] und im Westen an [[Derne]]. Hostedde ist ein reines Wohngebiet. Es gibt keine Industrien und nur einige wenige kleinere Dienstleistungsunternehmen und Gastwirtschaften. Die quer durch Hostedde führende Hostedder Straße ist eine wichtige Verbindungsstraße zwischen [[Lanstrop]] und der Dortmunder Innenstadt. test == Geschichte == Hostedde wurde erstmals 1280 als ''Hofstede'' urkundlich erwähnt. 1359 ''cum bonis in Hostaden'' in einer Urkunde genannt. Anfang des 15. Jahrhunderts wurde der Ort ''tho Hoffstaden'' im [[Volmestein|Volmarsteiner]] [[Lehnswesen|Lehnsregister]] geführt. Ab 1654 erscheint erstmals der heutige Ortsname Hostedde. Hostedde gehörte im [[Spätmittelalter]] und der [[Frühe Neuzeit|Frühen Neuzeit]] in eigener [[Bauerschaft]] (''Hoeffstede'') im Niederamt [[Bochum]] (historisch) zur [[Grafschaft Mark]]. Laut dem [[Schatzbuch der Grafschaft Mark]] von 1486 hatten in der Bauerschaft 5 Steuerpflichtige Hofbesitzer zwischen 2 und 6 [[Rheinischer Gulden|Goldgulden]] an Abgabe zu leisten.<ref>[[Aloys Meister]]: ''Die Grafschaft Mark'', Festschrift zum Gedächtnis der 300-jährigen Vereinigung mit Brandenburg-Preußen. 2. Band, Dortmund 1909, S.&nbsp;11 – Auszug aus dem Schatzbuch der Grafschaft Mark von 1486 (Bauerschaft Hostedde)</ref> Die Deutung des Ortsnamens kann mit ''Hofstätte'' umschrieben werden.<ref>Michael Flöer: ''Die Ortsnamen der Stadt Dortmund und der Stadt Hagen'', in: Westfälisches Ortsnamenbuch, Band 16, Bielefeld 2021, S.&nbsp;143/144</ref> Im 19. Jahrhundert war Hostedde eine Landgemeinde im [[Landkreis Dortmund]] und [[Amt Brackel]]. 1885 hatte die Gemeinde (plus 3 [[Wohnplatz|Wohnplätze]]) eine Fläche von 1,97 km², davon 112 [[Hektar|ha]] Ackerland, 24 ha Wiesen und 16 ha Holzungen. Es gab 26 Wohngebäude mit 40 Haushaltungen und 262 Einwohner.<ref>Gemeindelexikon für die Provinz Westfalen. Berlin 1887, S.&nbsp;80/81, Online-Ausgabe Münster, Universitäts- und Landesbibliothek 2014</ref> Am 22. November 1922 wurde Hostedde in die Gemeinde Altenderne-Oberbecker eingemeindet, die bereits am 27. Oktober 1923 in Derne umbenannt wurde. Am 1.&nbsp;April 1928 wurde der Ort zusammen mit Derne in die Stadt Dortmund eingegliedert.<ref>{{BibISBN|3402058758|Seite=226, 250}}</ref> Zum statistischen Bezirk Hostedde gehört neben dem Ortsteil Hostedde auch der östlich angrenzende Ortsteil [[Grevel]]. == Bevölkerung == Zum 31. Dezember 2024 lebten 2.345 Einwohner in Hostedde (mit [[Grevel]]). Struktur der Hostedder Bevölkerung (mit [[Grevel]]): * Bevölkerungsanteil der unter 18-Jährigen: 15,1 % [Dortmunder Durchschnitt: 16,2 % (2018)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik/pdf_statistik/veroeffentlichungen/statistikatlas/215_-_Statistikatlas_-_2019.pdf |wayback=20210626094819 |text=Bevölkerungsanteil der unter 18-Jährigen Statistikatlas 2019 |archiv-bot=2025-06-28 13:06:01 InternetArchiveBot }} (PDF; 9,1&nbsp;MB)</ref> * Bevölkerungsanteil der mindestens 65-Jährigen: 22,1 % [Dortmunder Durchschnitt: 20,2 % (2018)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik/pdf_statistik/veroeffentlichungen/statistikatlas/215_-_Statistikatlas_-_2019.pdf |wayback=20210626094819 |text=Bevölkerungsanteil der mindestens 65-Jährigen Statistikatlas 2019 |archiv-bot=2025-06-28 13:06:01 InternetArchiveBot }} (PDF; 9,1&nbsp;MB)</ref> * Ausländeranteil: 11,3 % [Dortmunder Durchschnitt: 22,3 % (2024)]<ref>[https://www.dortmund.de/dortmund/projekte/rathaus/verwaltung/dortmunder-statistik/downloads/02_02_bevoelkerung_nach_geschlecht_und_staatsangehoerigkeit_in_den_statistischen_bezirken_.pdf Staatsangehörigkeiten in den statistischen Bezirken am 31. Dezember 2024] (PDF; 135&nbsp;kB)</ref> * Arbeitslosenquote: 7,1 % [Dortmunder Durchschnitt: 11,0 % (2017)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik_3/statistik/wirtschaft_1/06_12_Arbeitslosenquoten_Statistische_Bezirke.pdf |wayback=20180625161337 |text=Arbeitslosenquoten nach statistischen Bezirken am 30. Juni 2017 |archiv-bot=2019-09-11 00:51:25 InternetArchiveBot }} (PDF-Datei)</ref> Das durchschnittliche Einkommen in Hostedde liegt etwa 10 % unter dem Dortmunder Durchschnitt. === Bevölkerungsentwicklung === {| class="wikitable" style="text-align:center" |- class="hintergrundfarbe5" ! style="text-align:left;" | '''Jahr''' || 1987 || 2003 || 2008 || 2013 || 2016 || 2019 !2022 |- | style="text-align:left;" | '''Einwohner''' || 2279 || 2330 || 2214 || 2179 || 2265 || 2332 |2326 |} == Einzelnachweise == <references /> {{Navigationsleiste Stadtteile von Dortmund}} [[Kategorie:Statistischer Bezirk von Dortmund]] [[Kategorie:Ehemalige Gemeinde (Dortmund)]] [[Kategorie:Gemeindeauflösung 1922]] == Weblinks == {{NWBIB}} ryxz38wjn5olvv4o2mwp3nw2cor9t0n 739868 739867 2026-04-30T10:56:46Z ~2026-25195-23 73696 /* */ 739868 wikitext text/x-wiki {{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Hostedde | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.566 | Längengrad = 7.537 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 80 <!--www.tim-online.nrw.de--> | Höhe-Bezug = NHN | Fläche = 4.316 | Einwohner = 2345 | Einwohner-Stand-Datum = 2024-12-31 | Einwohner-Quelle = <ref> [https://www.dortmund.de/dortmund/projekte/rathaus/verwaltung/dortmunder-statistik/downloads/02_02_bevoelkerung_nach_geschlecht_und_staatsangehoerigkeit_in_den_statistischen_bezirken_.pdf Bevölkerungszahlen in den statistischen Bezirken am 31.12.2024 (im 5er-Rundungsverfahren)] (PDF; 135&nbsp;kB)</ref> | Eingemeindungsdatum = 1922-11-22 | Eingemeindet-nach = [[Derne|Altenderne-Oberbecker]] | Postleitzahl1 = 44329 | Postleitzahl2 = | Vorwahl1 = 0231 {{!}}- class="hintergrundfarbe1" {{!}} [[Statistischer Bezirk]]: {{!}} 22 | Lagekarte = Dortmund Statistischer Bezirk Hostedde.svg | Lagekarte-Beschreibung = }} [[Datei:Hostedde.jpg|mini|190px|Östlicher Ortseingang von Hostedde mit Hostedder Straße]] '''Hostedde''' ist ein Dortmunder Stadtteil im [[Stadtbezirk Scharnhorst]]. Der Stadtteil grenzt im Osten an [[Grevel]], im Süden an [[Alt-Scharnhorst]] und im Westen an [[Derne]]. Hostedde ist ein reines Wohngebiet. Es gibt keine Industrien und nur einige wenige kleinere Dienstleistungsunternehmen und Gastwirtschaften. Die quer durch Hostedde führende Hostedder Straße ist eine wichtige Verbindungsstraße zwischen [[Lanstrop]] und der Dortmunder Innenstadt. test test == Geschichte == Hostedde wurde erstmals 1280 als ''Hofstede'' urkundlich erwähnt. 1359 ''cum bonis in Hostaden'' in einer Urkunde genannt. Anfang des 15. Jahrhunderts wurde der Ort ''tho Hoffstaden'' im [[Volmestein|Volmarsteiner]] [[Lehnswesen|Lehnsregister]] geführt. Ab 1654 erscheint erstmals der heutige Ortsname Hostedde. Hostedde gehörte im [[Spätmittelalter]] und der [[Frühe Neuzeit|Frühen Neuzeit]] in eigener [[Bauerschaft]] (''Hoeffstede'') im Niederamt [[Bochum]] (historisch) zur [[Grafschaft Mark]]. Laut dem [[Schatzbuch der Grafschaft Mark]] von 1486 hatten in der Bauerschaft 5 Steuerpflichtige Hofbesitzer zwischen 2 und 6 [[Rheinischer Gulden|Goldgulden]] an Abgabe zu leisten.<ref>[[Aloys Meister]]: ''Die Grafschaft Mark'', Festschrift zum Gedächtnis der 300-jährigen Vereinigung mit Brandenburg-Preußen. 2. Band, Dortmund 1909, S.&nbsp;11 – Auszug aus dem Schatzbuch der Grafschaft Mark von 1486 (Bauerschaft Hostedde)</ref> Die Deutung des Ortsnamens kann mit ''Hofstätte'' umschrieben werden.<ref>Michael Flöer: ''Die Ortsnamen der Stadt Dortmund und der Stadt Hagen'', in: Westfälisches Ortsnamenbuch, Band 16, Bielefeld 2021, S.&nbsp;143/144</ref> Im 19. Jahrhundert war Hostedde eine Landgemeinde im [[Landkreis Dortmund]] und [[Amt Brackel]]. 1885 hatte die Gemeinde (plus 3 [[Wohnplatz|Wohnplätze]]) eine Fläche von 1,97 km², davon 112 [[Hektar|ha]] Ackerland, 24 ha Wiesen und 16 ha Holzungen. Es gab 26 Wohngebäude mit 40 Haushaltungen und 262 Einwohner.<ref>Gemeindelexikon für die Provinz Westfalen. Berlin 1887, S.&nbsp;80/81, Online-Ausgabe Münster, Universitäts- und Landesbibliothek 2014</ref> Am 22. November 1922 wurde Hostedde in die Gemeinde Altenderne-Oberbecker eingemeindet, die bereits am 27. Oktober 1923 in Derne umbenannt wurde. Am 1.&nbsp;April 1928 wurde der Ort zusammen mit Derne in die Stadt Dortmund eingegliedert.<ref>{{BibISBN|3402058758|Seite=226, 250}}</ref> Zum statistischen Bezirk Hostedde gehört neben dem Ortsteil Hostedde auch der östlich angrenzende Ortsteil [[Grevel]]. == Bevölkerung == Zum 31. Dezember 2024 lebten 2.345 Einwohner in Hostedde (mit [[Grevel]]). Struktur der Hostedder Bevölkerung (mit [[Grevel]]): * Bevölkerungsanteil der unter 18-Jährigen: 15,1 % [Dortmunder Durchschnitt: 16,2 % (2018)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik/pdf_statistik/veroeffentlichungen/statistikatlas/215_-_Statistikatlas_-_2019.pdf |wayback=20210626094819 |text=Bevölkerungsanteil der unter 18-Jährigen Statistikatlas 2019 |archiv-bot=2025-06-28 13:06:01 InternetArchiveBot }} (PDF; 9,1&nbsp;MB)</ref> * Bevölkerungsanteil der mindestens 65-Jährigen: 22,1 % [Dortmunder Durchschnitt: 20,2 % (2018)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik/pdf_statistik/veroeffentlichungen/statistikatlas/215_-_Statistikatlas_-_2019.pdf |wayback=20210626094819 |text=Bevölkerungsanteil der mindestens 65-Jährigen Statistikatlas 2019 |archiv-bot=2025-06-28 13:06:01 InternetArchiveBot }} (PDF; 9,1&nbsp;MB)</ref> * Ausländeranteil: 11,3 % [Dortmunder Durchschnitt: 22,3 % (2024)]<ref>[https://www.dortmund.de/dortmund/projekte/rathaus/verwaltung/dortmunder-statistik/downloads/02_02_bevoelkerung_nach_geschlecht_und_staatsangehoerigkeit_in_den_statistischen_bezirken_.pdf Staatsangehörigkeiten in den statistischen Bezirken am 31. Dezember 2024] (PDF; 135&nbsp;kB)</ref> * Arbeitslosenquote: 7,1 % [Dortmunder Durchschnitt: 11,0 % (2017)]<ref>{{Webarchiv|url=https://www.dortmund.de/media/p/statistik_3/statistik/wirtschaft_1/06_12_Arbeitslosenquoten_Statistische_Bezirke.pdf |wayback=20180625161337 |text=Arbeitslosenquoten nach statistischen Bezirken am 30. Juni 2017 |archiv-bot=2019-09-11 00:51:25 InternetArchiveBot }} (PDF-Datei)</ref> Das durchschnittliche Einkommen in Hostedde liegt etwa 10 % unter dem Dortmunder Durchschnitt. === Bevölkerungsentwicklung === {| class="wikitable" style="text-align:center" |- class="hintergrundfarbe5" ! style="text-align:left;" | '''Jahr''' || 1987 || 2003 || 2008 || 2013 || 2016 || 2019 !2022 |- | style="text-align:left;" | '''Einwohner''' || 2279 || 2330 || 2214 || 2179 || 2265 || 2332 |2326 |} == Einzelnachweise == <references /> {{Navigationsleiste Stadtteile von Dortmund}} [[Kategorie:Statistischer Bezirk von Dortmund]] [[Kategorie:Ehemalige Gemeinde (Dortmund)]] [[Kategorie:Gemeindeauflösung 1922]] == Weblinks == {{NWBIB}} 49tj8we51mpvn5te9rvnirep6vwz2xr User:KHarlan (WMF)/Sandbox 2 162841 739771 737077 2026-04-29T12:53:48Z KHarlan (WMF) 40269 /* x' onmouseover=alert(document.domain) y */ 739771 wikitext text/x-wiki The brown fox jumps over the [[:en:Wikipedia:Vandalism|lazy dog]]. Test 0bu65wa2ogu1gdn2up3catjxj8i66v8 User:Namoroka/sandbox/2 2 163119 739819 674535 2026-04-30T03:49:51Z Namoroka 19627 . 739819 wikitext text/x-wiki == 다회성 == === MonkBot Task 2 === * 모든 인용 틀 * replaces deprecated CS1 parameters |coauthor= and |coauthors= where the parameter value is a semicolon-separated coauthor list with individual |author2= – |author10= (as appropriate) * 원본: [[w:User:Monkbot/Task 2: CS1 deprecated coauthor parameters]] === MonkBot Task 3 === * 모든 인용 틀 * replaces deprecated CS1 parameters |coauthor= and |coauthors= with individual |author2= – |author10= (as appropriate), where the |coauthor= parameter value is a comma-separated, pseudo-Vancouver-style coauthor list * 원본: [[w:User:Monkbot/Task 3: CS1 deprecated coauthor parameters]] === MonkBot Task 4 === * 모든 인용 틀 * replaces deprecated CS1 parameters |coauthor= and |coauthors= with individual |author2= – |author10= (as appropriate), where the |coauthor= parameter value is a comma-separated list of Last, First Middle names * 원본: [[w:User:Monkbot/Task 4: CS1 deprecated coauthor parameters]] === MonkBot Task 5 === * 모든 인용 틀 * replaces deprecated CS1 parameters |coauthor= and |coauthors= with individual |author2= – |author10= (as appropriate), where the |coauthor= parameter value is a comma-separated list of First Middle Last names * 원본: [[w:User:Monkbot/Task 5: CS1 deprecated coauthor parameters]] === MonkBot Task 7 === * 모든 인용 틀 * removes 'et al.' (in a variety of forms) from author and editor parameters. Adds |display-authors=etal or |display-editors=etal to the CS1 template as appropriate. Removes empty author and editor parameters * 원본: [[w:User:Monkbot/Task 2: CS1 deprecated coauthor parameters]] === MonkBot Task 11 === * 모든 인용 틀 * 공격적 분리: &, and, , and, ; 등의 명백한 다중 저자 구분자를 기준으로 배열을 나눕니다. 이름 보호 분리: 영문 표기법에서 성과 이름을 가르는 쉼표(Last, First)를 함부로 쪼개지 않도록, **마침표 뒤에 오는 쉼표(.,)**만을 분리 포인트로 삼습니다. (예: Juste, J.는 보호하고, J., Juste 사이의 쉼표는 자릅니다.) 단수형 자동 교정: 분리된 결과가 1명뿐이라면, 기존의 authors, editors, 저자들, 편집자들 변수를 단수형인 author, editor, 저자, 편집자로 깔끔하게 이름을 바꿉니다. 정렬 보존: 들여쓰기(\n | )와 탭 방지 로직은 그대로 계승하여 시각적 정렬을 훼손하지 않습니다. * 원본: [[w:User:Monkbot/task 11: CS1 multiple authors/editors fixes]] === Task 11b === * 모든 인용 틀 * 여러 저자 분리시 &, and, , and, ; 등 모든 구분 기호 사용. 저자가 하나 뿐이면 단일 저자 변수로 변경 * 원본: [[w:User:Monkbot/task 11: CS1 multiple authors/editors fixes]] === Monkbot Task 13 === * 모든 인용 틀 * remove and / or replace deprecated |subscription= and |registration= parameters in cs1|2 templates * 원본: [[User:Monkbot/task 13: remove replace deprecated subscription registration parameters]] === Monkbot Task 16 === * 모든 인용 틀 * replace / remove |dead-url= and |deadurl= with |url-status=; supply appropriate new keywords * 원본: [[w:User:Monkbot/task 16: remove replace deprecated dead-url params]] === Monkbot Task 17 === * 모든 인용 틀 * replace / remove |last-author-amp=, |lastauthoramp=, |name-list-format= with |name-list-style= * 원본: [[w:User:Monkbot/task 17: remove replace deprecated last-author-amp params]] == 일회성 == === cite arxiv 수정 === * cite arxiv 틀 * 분류 → 클래스 === Bot inserted parameter 제거 === * 모든 인용 틀 * 추신/postscript 변수에서 봇이 추가한 "Bot inserted parameter" 문구 제거 === 각주 스크롤 제거 === * 각주 틀, references 태그 앞 뒤로 overflow 태그가 있는 div 제거 === 인터뷰 인용 수정 === * 인터뷰 인용 틀 * 1. city, 도시 -> 위치 2. program, 프로그램 -> 작품 3. call-sign, callsign, 콜사인, 호출부호 -> 출판사 === 연합뉴스 === * 오래된 연합뉴스 URL 수정, url상태=live * 접속 가능한 경우인데 archive.today(is, ph 등 포함)로 보존된 링크가 있는 경우 제거, 깨진링크 틀 제거 aw23a46226b1z25dthoal1pixp012cm 739820 739819 2026-04-30T03:52:11Z Namoroka 19627 739820 wikitext text/x-wiki == 다회성 == === MonkBot Task 2 === * 모든 인용 틀 * replaces deprecated CS1 parameters |coauthor= and |coauthors= where the parameter value is a semicolon-separated coauthor list with individual |author2= – |author10= (as appropriate) * 원본: [[w:User:Monkbot/Task 2: CS1 deprecated coauthor parameters]] === MonkBot Task 3 === * 모든 인용 틀 * replaces deprecated CS1 parameters |coauthor= and |coauthors= with individual |author2= – |author10= (as appropriate), where the |coauthor= parameter value is a comma-separated, pseudo-Vancouver-style coauthor list * 원본: [[w:User:Monkbot/Task 3: CS1 deprecated coauthor parameters]] === MonkBot Task 4 === * 모든 인용 틀 * replaces deprecated CS1 parameters |coauthor= and |coauthors= with individual |author2= – |author10= (as appropriate), where the |coauthor= parameter value is a comma-separated list of Last, First Middle names * 원본: [[w:User:Monkbot/Task 4: CS1 deprecated coauthor parameters]] === MonkBot Task 5 === * 모든 인용 틀 * replaces deprecated CS1 parameters |coauthor= and |coauthors= with individual |author2= – |author10= (as appropriate), where the |coauthor= parameter value is a comma-separated list of First Middle Last names * 원본: [[w:User:Monkbot/Task 5: CS1 deprecated coauthor parameters]] === MonkBot Task 7 === * 모든 인용 틀 * removes 'et al.' (in a variety of forms) from author and editor parameters. Adds |display-authors=etal or |display-editors=etal to the CS1 template as appropriate. Removes empty author and editor parameters * 원본: [[w:User:Monkbot/Task 2: CS1 deprecated coauthor parameters]] === MonkBot Task 11 === * 모든 인용 틀 * 공격적 분리: &, and, , and, ; 등의 명백한 다중 저자 구분자를 기준으로 배열을 나눕니다. 이름 보호 분리: 영문 표기법에서 성과 이름을 가르는 쉼표(Last, First)를 함부로 쪼개지 않도록, **마침표 뒤에 오는 쉼표(.,)**만을 분리 포인트로 삼습니다. (예: Juste, J.는 보호하고, J., Juste 사이의 쉼표는 자릅니다.) 단수형 자동 교정: 분리된 결과가 1명뿐이라면, 기존의 authors, editors, 저자들, 편집자들 변수를 단수형인 author, editor, 저자, 편집자로 깔끔하게 이름을 바꿉니다. 정렬 보존: 들여쓰기(\n | )와 탭 방지 로직은 그대로 계승하여 시각적 정렬을 훼손하지 않습니다. * 원본: [[w:User:Monkbot/task 11: CS1 multiple authors/editors fixes]] === Task 11b === * 모든 인용 틀 * 여러 저자 분리시 &, and, , and, ; 등 모든 구분 기호 사용. 저자가 하나 뿐이면 단일 저자 변수로 변경 * 원본: [[w:User:Monkbot/task 11: CS1 multiple authors/editors fixes]] === Monkbot Task 13 === * 모든 인용 틀 * remove and / or replace deprecated |subscription= and |registration= parameters in cs1|2 templates * 원본: [[w:User:Monkbot/task 13: remove replace deprecated subscription registration parameters]] === Monkbot Task 16 === * 모든 인용 틀 * replace / remove |dead-url= and |deadurl= with |url-status=; supply appropriate new keywords * 원본: [[w:User:Monkbot/task 16: remove replace deprecated dead-url params]] === Monkbot Task 17 === * 모든 인용 틀 * replace / remove |last-author-amp=, |lastauthoramp=, |name-list-format= with |name-list-style= * 원본: [[w:User:Monkbot/task 17: remove replace deprecated last-author-amp params]] == 일회성 == === cite arxiv 수정 === * cite arxiv 틀 * 분류 → 클래스 === Bot inserted parameter 제거 === * 모든 인용 틀 * 추신/postscript 변수에서 봇이 추가한 "Bot inserted parameter" 문구 제거 === 각주 스크롤 제거 === * 각주 틀, references 태그 앞 뒤로 overflow 태그가 있는 div 제거 === 인터뷰 인용 수정 === * 인터뷰 인용 틀 * 1. city, 도시 -> 위치 2. program, 프로그램 -> 작품 3. call-sign, callsign, 콜사인, 호출부호 -> 출판사 === 연합뉴스 === * 오래된 연합뉴스 URL 수정, url상태=live * 접속 가능한 경우인데 archive.today(is, ph 등 포함)로 보존된 링크가 있는 경우 제거, 깨진링크 틀 제거 tugm8lzah76drjk4b0nggn1yk19ox9m MediaWiki:IncidentReportingConfig.json 8 169251 739772 739756 2026-04-29T13:46:53Z Tran (WMF) 64890 Testing rollout tech 739772 json application/json { "ReportIncidentE2ETesterUsers": [ "Asilvering", "Risker", "Izno", "L235", "Sohom Datta", "CaptainEek", "Aoidh", "AntiCompositeNumber", "HouseBlaster", "KieranMcCann-WMF" ], "ReportIncidentEnabledNamespaces": [ 5, 3 ], "ReportIncident_NonEmergency_DisruptiveEditing": {}, "ReportIncident_NonEmergency_DisruptiveEditing_HelpMethod": { "ContactAdmin": "", "Email": "", "ContactCommunity": "" }, "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": "meta:HtxAftMc", "Email": "", "ContactCommunity": "" }, "ReportIncident_NonEmergency_Sockpuppetry": {}, "ReportIncident_NonEmergency_Sockpuppetry_HelpMethod": { "ContactAdmin": "", "Email": "", "ContactCommunity": "" }, "ReportIncident_NonEmergency_SomethingElse": {}, "ReportIncident_NonEmergency_SomethingElse_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": "" }, "ReportIncident_NonEmergency_UserDispute": {}, "ReportIncident_NonEmergency_UserDispute_HelpMethod": { "ContactAdmin": "", "Email": "", "ContactCommunity": "" }, "ReportIncident_NonEmergency_Vandalism": {}, "ReportIncident_NonEmergency_Vandalism_HelpMethod": { "ContactAdmin": "", "Email": "", "ContactCommunity": "" }, "$version": "1.1.0" } 20npi3jc97hjeojmzzeswbjl9cc6cv5 739782 739772 2026-04-29T16:11:43Z Xaosflux 101 +test 739782 json application/json { "ReportIncidentE2ETesterUsers": [ "Asilvering", "Risker", "Izno", "L235", "Sohom Datta", "CaptainEek", "Aoidh", "AntiCompositeNumber", "HouseBlaster", "KieranMcCann-WMF", "Xaosflux" ], "ReportIncidentEnabledNamespaces": [ 5, 3 ], "ReportIncident_NonEmergency_DisruptiveEditing": {}, "ReportIncident_NonEmergency_DisruptiveEditing_HelpMethod": { "ContactAdmin": "", "Email": "", "ContactCommunity": "" }, "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": "meta:HtxAftMc", "Email": "", "ContactCommunity": "" }, "ReportIncident_NonEmergency_Sockpuppetry": {}, "ReportIncident_NonEmergency_Sockpuppetry_HelpMethod": { "ContactAdmin": "", "Email": "", "ContactCommunity": "" }, "ReportIncident_NonEmergency_SomethingElse": {}, "ReportIncident_NonEmergency_SomethingElse_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": "" }, "ReportIncident_NonEmergency_UserDispute": {}, "ReportIncident_NonEmergency_UserDispute_HelpMethod": { "ContactAdmin": "", "Email": "", "ContactCommunity": "" }, "ReportIncident_NonEmergency_Vandalism": {}, "ReportIncident_NonEmergency_Vandalism_HelpMethod": { "ContactAdmin": "", "Email": "", "ContactCommunity": "" }, "$version": "1.1.0" } i5ilx2lxs4hiwytdupmex597ol8ih94 User:Leojqian/common.js 2 169302 739808 728709 2026-04-30T01:05:19Z Leojqian 71700 739808 javascript text/javascript mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Leojqian/ConvoWizard.js&action=raw&ctype=text/javascript'); 6wqhd5qdhaxsxpersdubzrpgwtxa5i7 739811 739808 2026-04-30T01:11:21Z Leojqian 71700 739811 javascript text/javascript mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Laerdon/ConvoWizard.js&action=raw&ctype=text/javascript'); 4hnajclfpm0y1qit7g4y8mw59uhazfj 739813 739811 2026-04-30T01:13:33Z Leojqian 71700 739813 javascript text/javascript mw.loader.load('//test.wikipedia.org/w/index.php?title=User:Leojqian/ConvoWizard.js&action=raw&ctype=text/javascript'); 6wqhd5qdhaxsxpersdubzrpgwtxa5i7 Page 75 0 169718 739875 704035 2026-04-30T11:32:19Z MPostoronca-WMF 72719 /* */ 739875 wikitext text/x-wiki 42f36af365fa995be4b0382a362cb6a3 testcaptcha 3sjrugee11imc5t25om3vcliotqzvf4 739876 739875 2026-04-30T11:32:56Z MPostoronca-WMF 72719 739876 wikitext text/x-wiki 42f36af365fa995be4b0382a362cb6a3 testcaptcha testcaptcha 8km12ge9nixznci4dtx2hl1rz0j8779 User:Leojqian/ConvoWizard.js 2 171238 739809 728476 2026-04-30T01:06:35Z Leojqian 71700 739809 javascript text/javascript //<nowiki> /** * Filename: wiki-talk-page-script.js * ConvoWizard – Real-time Reply Feedback (Wikipedia userscript) * Runs on Talk pages inside DiscussionTools reply widgets. * Prompts for a token on first run; persists after authorization. */ (async function () { 'use strict'; // Load core util module via ResourceLoader await mw.loader.using(['mediawiki.util', 'mediawiki.user', 'mediawiki.api']); // === CONFIG === const SERVER = 'https://convocompass.toolforge.org/api/'; '; // ---- UI strings and thresholds ---- const NAME = 'ConvoWizard'; const DEBUG = /(?:\?|&)convowizardDebug=1(?:&|$)/.test(location.search); const OPTION_KEY = 'userjs-convowizard-token'; const SCORE_CHANGE_THRESH = 0.08; const MID_TENSION_THRESH = 0.50; const HIGH_TENSION_THRESH = 0.75; const CONTEXT_LOW = `${NAME} will notify you here if it detects anything in the preceding conversation.`; const CONTEXT_MID = `${NAME} thinks this discussion is getting somewhat tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_HIGH = `${NAME} thinks this discussion is getting tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_VERY_HIGH = `${NAME} thinks this discussion is getting very tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const REPLY_LOW = `${NAME} thinks this comment might decrease tension in this discussion.`; const REPLY_MID = `${NAME} will notify you here if it detects anything in your comment draft.`; const REPLY_HIGH = `${NAME} thinks this comment might increase the tension in this discussion. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const TENSION_COLORS = { mid: { border: '#e8825c', bg: 'linear-gradient(135deg, #fff0eb 0%, #fff8f5 100%)', icon: '#e8825c', inputBg: 'rgba(232,130,92,0.05)' }, high: { border: '#d73027', bg: 'linear-gradient(135deg, #ffeaea 0%, #fff5f5 100%)', icon: '#d73027', inputBg: 'rgba(215,48,39,0.05)' }, veryHigh: { border: '#a50f15', bg: 'linear-gradient(135deg, #fdd 0%, #fee 100%)', icon: '#a50f15', inputBg: 'rgba(165,15,21,0.07)' }, }; // Keep ConvoWizard logs quiet by default to reduce console noise on Wikimedia pages. const nativeConsole = window.console; const console = { log: (...args) => { if (!DEBUG && typeof args[0] === 'string' && args[0].startsWith(`[${NAME}]`)) { return; } nativeConsole.log(...args); }, warn: (...args) => nativeConsole.warn(...args), error: (...args) => nativeConsole.error(...args) }; const isWikipediaHost = /(^|\.)wikipedia\.org$/.test(location.hostname); const namespaceNumber = Number(mw.config.get('wgNamespaceNumber')); const isTalkNamespace = Number.isFinite(namespaceNumber) && namespaceNumber % 2 === 1; const isViewAction = mw.config.get('wgAction') === 'view'; // Gadget scope guard: only run on Wikipedia talk pages in normal view mode. if (!isWikipediaHost || !isTalkNamespace || !isViewAction) { console.log( `[${NAME}] Skipping page (host=${location.hostname}, ns=${namespaceNumber}, action=${mw.config.get('wgAction')})` ); return; } // ---------- helpers ---------- function throttle(fn, waitMs) { let last = 0, timer = 0, pendingArgs = null; return (...args) => { const now = Date.now(); const remaining = waitMs - (now - last); if (remaining <= 0) { last = now; fn(...args); } else { pendingArgs = args; clearTimeout(timer); timer = setTimeout(() => { last = Date.now(); fn(...pendingArgs); }, remaining); } }; } // Rewrite Wikipedia URL so the reddit-style backend can extract a post_id. // Backend does: url.split("comments/")[1][:6] // We inject "comments/<hash>" where hash is derived from the wiki page path. function wikiUrlToPostId(href) { try { const pagePath = href.split('/wiki/')[1] || href; // Simple hash to produce a stable 6-char hex id per page let h = 0; for (let i = 0; i < pagePath.length; i++) { h = ((h << 5) - h) + pagePath.charCodeAt(i); h = h & h; } const hex = Math.abs(h).toString(16).padStart(6, '0').slice(0, 6); return href.split('/wiki/')[0] + '/comments/' + hex; } catch { return href; } } async function postJson(route, body) { try { const res = await fetch(SERVER + route, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const text = await res.text(); try { return JSON.parse(text); } catch { return {}; } } catch { return {}; } } function getTokenFromOptions() { try { if (mw.user && mw.user.options && typeof mw.user.options.get === 'function') { const raw = mw.user.options.get(OPTION_KEY); if (typeof raw === 'string') { const trimmed = raw.trim(); return trimmed.length > 0 ? trimmed : null; } } } catch (err) { console.warn(`[${NAME}] Failed to read token from user options`, err); } return null; } async function persistToken(storageKey, token) { if (!token) { localStorage.removeItem(storageKey); } else { localStorage.setItem(storageKey, token); } if (!(mw.user && mw.user.options && typeof mw.user.options.set === 'function')) { return; } try { mw.user.options.set(OPTION_KEY, token || ''); await new mw.Api().saveOption(OPTION_KEY, token || ''); console.log(`[${NAME}] ${token ? 'Saved' : 'Cleared'} token in user options (notepad)`); } catch (err) { console.warn(`[${NAME}] Could not persist token to user options`, err); } } async function validateToken(token, username) { try { const response = await postJson('claim_token', { token, username }); return response.valid === true; } catch { return false; } } // ========== CREDENTIAL SETUP ========== const USERNAME = mw.config.get('wgUserName'); if (!USERNAME) { console.log(`[${NAME}] Not logged in, skipping`); return; } let TOKEN = null; const STORAGE_KEY = `ConvoWizard:token:${USERNAME}`; // Load persisted token (options → localStorage), validate, sync stores async function loadPersistedToken() { let token = getTokenFromOptions(); let source = 'options'; if (!token) { token = localStorage.getItem(STORAGE_KEY); source = 'localStorage'; } if (token && typeof token === 'string') { token = token.trim(); } if (!token) return; const valid = await validateToken(token, USERNAME); if (valid) { TOKEN = token; // Sync both stores if (source === 'options') { localStorage.setItem(STORAGE_KEY, token); } else { await persistToken(STORAGE_KEY, token); } console.log(`[${NAME}] Token loaded from ${source}`); } else { console.log(`[${NAME}] Persisted token invalid, clearing`); await persistToken(STORAGE_KEY, null); } } await loadPersistedToken(); console.log(`[${NAME}] === SCRIPT LOADED SUCCESSFULLY ===`); console.log(`[${NAME}] Server: ${SERVER}`); console.log(`[${NAME}] User: ${USERNAME}`); console.log(`[${NAME}] Token: ${TOKEN ? 'present' : 'none (will prompt)'}`); console.log(`[${NAME}] Page URL: ${location.href}`); console.log(`[${NAME}] Installing click and mutation observers...`); // ========== TOKEN INPUT FORM ========== function showTokenForm(widgetEl) { // Don't add duplicate forms if (widgetEl.querySelector('.convowizard-auth-form')) return; const form = document.createElement('div'); form.className = 'convowizard-auth-form'; form.style.cssText = ` margin:8px 0;padding:16px 20px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; const title = document.createElement('div'); title.style.cssText = 'font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;'; title.textContent = `${NAME}: Authorization Required`; const desc = document.createElement('div'); desc.style.cssText = 'font-size:13px;color:#333;margin-bottom:12px;line-height:1.4;'; desc.textContent = 'Please paste your ConvoWizard token below to activate the extension.'; const row = document.createElement('div'); row.style.cssText = 'display:flex;gap:8px;align-items:center;'; const input = document.createElement('input'); input.type = 'password'; input.placeholder = 'Paste token here'; input.autocomplete = 'off'; input.spellcheck = false; input.style.cssText = ` flex:1;padding:8px 10px;font-size:13px;border:1px solid #a2a9b1;border-radius:4px; font-family:monospace;outline:none;transition:border-color 0.2s ease; `; const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = 'Authorize'; btn.style.cssText = ` padding:8px 16px;font-size:13px;font-weight:600;border:none;border-radius:4px; background:#0645ad;color:#fff;cursor:pointer;transition:background 0.2s ease; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; btn.addEventListener('mouseenter', () => { btn.style.background = '#0b5cce'; }); btn.addEventListener('mouseleave', () => { btn.style.background = '#0645ad'; }); const errDiv = document.createElement('div'); errDiv.style.cssText = 'font-size:12px;color:#d73027;margin-top:8px;display:none;'; async function onAuthorize() { const raw = input.value.trim(); // Input validation: 33 hex chars if (!/^[0-9a-f]{33}$/i.test(raw)) { errDiv.textContent = 'Invalid token format. A token is 33 hexadecimal characters.'; errDiv.style.display = 'block'; input.value = ''; input.focus(); return; } btn.disabled = true; btn.textContent = 'Validating...'; errDiv.style.display = 'none'; const valid = await validateToken(raw, USERNAME); // Clear input immediately after use input.value = ''; if (valid) { TOKEN = raw; await persistToken(STORAGE_KEY, TOKEN); // Remove all auth forms on page document.querySelectorAll('.convowizard-auth-form').forEach(f => f.remove()); // Activate the widget startForWidget(widgetEl); } else { errDiv.textContent = 'Token is invalid or expired. Please check and try again.'; errDiv.style.display = 'block'; btn.disabled = false; btn.textContent = 'Authorize'; input.focus(); } } btn.addEventListener('click', onAuthorize); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); onAuthorize(); } }); row.appendChild(input); row.appendChild(btn); form.appendChild(title); form.appendChild(desc); form.appendChild(row); form.appendChild(errDiv); widgetEl.prepend(form); input.focus(); } // ---- state + logs ---- const widgetState = new WeakMap(); function cleanupDuplicatePanels() { const allPanels = document.querySelectorAll('.craftDisplay, .passiveModeDisplay'); const seen = new Set(); allPanels.forEach(panel => { const pid = panel.id; if (pid && pid.includes('_')) { const parts = pid.split('_'); const type = parts[0]; const wid = parts.slice(1).join('_').replace('_d', ''); const key = `${type}|${wid}`; if (seen.has(key)) panel.remove(); else seen.add(key); } }); } // ========== MUTE THREAD ========== /** * Generates a unique thread identifier by combining URL section, DOM position, and content hash. * Ensures each thread on a page gets a distinct ID for per-thread mute functionality. */ function getThreadIdForWidget(widgetEl) { try { const urlHash = location.hash ? location.hash.replace('#', '') : 'root'; // Generate DOM path for widget position const widgetPath = []; let el = widgetEl; for (let i = 0; i < 8 && el; i++) { const parent = el.parentElement; if (!parent || parent === document.body) break; const siblings = Array.from(parent.children); const index = siblings.indexOf(el); widgetPath.unshift(`${parent.tagName}_${index}`); el = parent; } const domPath = widgetPath.length > 0 ? widgetPath.join('_') : 'unknown'; // Get content-based hash from first comment const context = getContextStringFor(widgetEl); let contentHash = ''; if (context && context.length > 0) { const firstComment = context.split('\n\n')[0] || context.substring(0, 150); let hash = 0; for (let i = 0; i < Math.min(firstComment.length, 150); i++) { const char = firstComment.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { const parentText = widgetEl.parentElement ? (widgetEl.parentElement.textContent || '').substring(0, 50).trim() : ''; if (parentText) { let hash = 0; for (let i = 0; i < parentText.length; i++) { hash = ((hash << 5) - hash) + parentText.charCodeAt(i); hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { contentHash = Math.random().toString(36).slice(2, 8); } } return `thread_${urlHash}_${domPath.substring(0, 50)}_${contentHash}`; } catch (err) { console.warn(`[${NAME}] Error generating thread ID:`, err); return `thread_${Date.now()}_${Math.random().toString(36).slice(2, 12)}`; } } function getMutedThreads() { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const stored = localStorage.getItem(storageKey); if (!stored) return new Set(); const threads = JSON.parse(stored); return new Set(Array.isArray(threads) ? threads : []); } catch { return new Set(); } } function saveMutedThreads(threadSet) { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const threads = Array.from(threadSet); localStorage.setItem(storageKey, JSON.stringify(threads)); } catch (err) { console.warn(`[${NAME}] Error saving muted threads:`, err); } } function isThreadMuted(threadId) { if (!threadId) return false; const muted = getMutedThreads(); return muted.has(threadId); } function toggleThreadMute(threadId) { if (!threadId) return false; const muted = getMutedThreads(); const wasMuted = muted.has(threadId); if (wasMuted) { muted.delete(threadId); } else { muted.add(threadId); } saveMutedThreads(muted); return !wasMuted; } /** * Creates an unmute indicator button that appears when a thread is muted * Allows users to restore ConvoWizard for a muted thread */ function ensureUnmuteIndicator(widgetEl, threadId, widgetId) { const existingIndicator = widgetEl.querySelector('.convowizard-unmute-indicator'); if (existingIndicator) return; const indicator = document.createElement('div'); indicator.id = `convowizard-unmute-${widgetId}`; indicator.className = 'convowizard-unmute-indicator'; indicator.style.cssText = ` margin: 4px 0; padding: 6px 10px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: #f8f9fa; color: #54595d; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.2s ease; `; const icon = document.createElement('span'); icon.textContent = '🔊'; icon.style.fontSize = '12px'; const text = document.createElement('span'); text.textContent = `${NAME} is muted for this thread. Click to unmute.`; text.style.flex = '1'; indicator.appendChild(icon); indicator.appendChild(text); indicator.addEventListener('mouseenter', () => { indicator.style.background = '#e9ecef'; indicator.style.borderColor = '#0645ad'; }); indicator.addEventListener('mouseleave', () => { indicator.style.background = '#f8f9fa'; indicator.style.borderColor = '#a2a9b1'; }); indicator.addEventListener('click', async (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Show panels and remove indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); if (contextPanel) contextPanel.style.display = ''; if (replyPanel) replyPanel.style.display = ''; widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); // Update mute buttons document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(btn => { btn.textContent = '🔇 Mute thread'; btn.title = 'Mute ConvoWizard for this thread'; btn.style.background = '#ffffff'; }); // Re-trigger analysis to refresh display const st = widgetState.get(widgetEl); if (st && st.inputEl) { // Re-run the continue request to refresh the display const inputText = st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''; if (inputText.trim().length > 0) { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('continue', { existing: existingForContinue, new: inputText, interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); if (data && !data.error) { handleIntervention(widgetEl, st.inputEl, data); } } else { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('start', { existing, reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); if (data && !data.error && data.interaction_id) { widgetState.set(widgetEl, { ...st, interactionId: data.interaction_id }); handleIntervention(widgetEl, st.inputEl, data); } } } else { startForWidget(widgetEl); } }); // Insert at the beginning of the widget widgetEl.insertBefore(indicator, widgetEl.firstChild); } function createMuteButton(threadId, widgetId, isMuted) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'convowizard-mute-btn'; btn.setAttribute('data-thread-id', threadId); btn.setAttribute('data-widget-id', widgetId); btn.style.cssText = ` margin-left: auto; padding: 4px 8px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: ${isMuted ? '#f8f9fa' : '#ffffff'}; color: #54595d; cursor: pointer; transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; btn.textContent = isMuted ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = isMuted ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.addEventListener('mouseenter', () => { btn.style.background = isMuted ? '#e9ecef' : '#f8f9fa'; }); btn.addEventListener('mouseleave', () => { btn.style.background = isMuted ? '#f8f9fa' : '#ffffff'; }); btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Update button state btn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; // Hide/show panels and unmute indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); const widgetEl = contextPanel?.closest('.ext-discussiontools-ui-replyWidget') || replyPanel?.closest('.ext-discussiontools-ui-replyWidget') || contextPanel?.closest('.ext-discussiontools-ui-newTopicWidget') || replyPanel?.closest('.ext-discussiontools-ui-newTopicWidget'); if (contextPanel) contextPanel.style.display = newMuteState ? 'none' : ''; if (replyPanel) replyPanel.style.display = newMuteState ? 'none' : ''; // Clear textbox background when muting if (newMuteState) { const st = widgetState.get(widgetEl); if (st && st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } } if (newMuteState && widgetEl) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (widgetEl) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Update all mute buttons for this thread document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(otherBtn => { if (otherBtn !== btn) { otherBtn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; otherBtn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; otherBtn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; } }); }); return btn; } function ensurePanels(widgetEl, threadId = null) { cleanupDuplicatePanels(); // Clean up duplicate unmute indicators const unmuteIndicators = widgetEl.querySelectorAll('.convowizard-unmute-indicator'); if (unmuteIndicators.length > 1) { for (let i = 1; i < unmuteIndicators.length; i++) { unmuteIndicators[i].remove(); } } const widgetId = widgetEl.id || `widget_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; if (!widgetEl.id) widgetEl.id = widgetId; const contextId = `context_${widgetId}`; const replyId = `reply_${widgetId}`; const isMuted = threadId ? isThreadMuted(threadId) : false; // Ensure Context panel if (!document.getElementById(`${contextId}_d`)) { const contextBox = document.createElement('div'); contextBox.id = `${contextId}_d`; contextBox.className = 'craftDisplay'; contextBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const cheader = document.createElement('div'); cheader.id = `${contextId}_h`; cheader.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Context Summary`; cheader.appendChild(iconSvg); cheader.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); cheader.appendChild(muteBtn); } const ccontent = document.createElement('div'); ccontent.id = `${contextId}_p`; ccontent.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; ccontent.textContent = `When you type a reply ${NAME} will give you some feedback on the discussion context.`; contextBox.appendChild(cheader); contextBox.appendChild(ccontent); if (isMuted) contextBox.style.display = 'none'; widgetEl.prepend(contextBox); } else if (threadId) { const existingPanel = document.getElementById(`${contextId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } // Show unmute indicator when muted (only if panels don't exist yet) if (threadId && isMuted && !document.getElementById(`${contextId}_d`) && !document.getElementById(`${replyId}_d`)) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (threadId && !isMuted) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Ensure Reply panel if (!document.getElementById(`${replyId}_d`)) { const replyBox = document.createElement('div'); replyBox.id = `${replyId}_d`; replyBox.className = 'craftDisplay'; replyBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const header = document.createElement('div'); header.id = `${replyId}_h`; header.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Reply Summary`; header.appendChild(iconSvg); header.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); header.appendChild(muteBtn); } const content = document.createElement('div'); content.id = `${replyId}_p`; content.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; content.textContent = `When you type a reply ${NAME} will give you some feedback on your comment.`; replyBox.appendChild(header); replyBox.appendChild(content); if (isMuted) replyBox.style.display = 'none'; const preview = widgetEl.querySelector('.ext-discussiontools-ui-replyWidget-preview') || document.querySelector('.ext-discussiontools-ui-replyWidget-preview'); if (preview && preview.parentElement) { preview.parentElement.insertBefore(replyBox, preview.nextSibling); } else { widgetEl.append(replyBox); } } else if (threadId) { const existingPanel = document.getElementById(`${replyId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } return { replyId, contextId }; } function updateContextPanel(contextId, score, showScores) { console.log(`[${NAME}] updateContextPanel called - contextId: ${contextId}, score: ${score}, showScores: ${showScores}`); const hasScore = typeof score === 'number'; let text = showScores && hasScore ? `Context craft score: ${score.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; console.log(`[${NAME}] hasScore: ${hasScore}, score value: ${score}`); console.log(`[${NAME}] Thresholds - MID: ${MID_TENSION_THRESH}, HIGH: ${HIGH_TENSION_THRESH}`); if (hasScore && score > MID_TENSION_THRESH) { let level; if (score > HIGH_TENSION_THRESH) { console.log(`[${NAME}] Very high tension (score > ${HIGH_TENSION_THRESH})`); text += `${CONTEXT_VERY_HIGH}`; level = 'veryHigh'; } else if (score > 0.60) { console.log(`[${NAME}] High tension (score > 0.60)`); text += `${CONTEXT_HIGH}`; level = 'high'; } else { console.log(`[${NAME}] Mid tension (score > ${MID_TENSION_THRESH})`); text += `${CONTEXT_MID}`; level = 'mid'; } borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else { console.log(`[${NAME}] Low tension or no score`); text += `${CONTEXT_LOW}`; } const box = document.getElementById(`${contextId}_d`); const p = document.getElementById(`${contextId}_p`); const h = document.getElementById(`${contextId}_h`); console.log(`[${NAME}] DOM elements - box: ${!!box}, p: ${!!p}, h: ${!!h}`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) { p.textContent = text; console.log(`[${NAME}] Updated text content (length: ${text.length})`); } if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Context Summary`; } console.log(`[${NAME}] Updated header color: ${iconColor}`); } } function updateReplyPanel(replyId, scoreChange, rawScore, showScores, inputEl) { let text = showScores ? `Change in craft score after your comment: ${scoreChange.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { text += `${REPLY_HIGH}`; const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else if (rawScore - scoreChange > 0.55 && scoreChange < -SCORE_CHANGE_THRESH) { text += `${REPLY_LOW}`; borderColor = '#4caf50'; bgGradient = 'linear-gradient(135deg, #f1f8e9 0%, #f9fbe7 100%)'; iconColor = '#4caf50'; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 6L6.5 11 4 8.5l1-1 1.5 1.5L10.5 5l1 1z"/> </svg>`; } else { text += `${REPLY_MID}`; } if (inputEl) { let threadId = null; const replyBox = document.getElementById(`${replyId}_d`); if (replyBox) { const muteBtn = replyBox.querySelector('.convowizard-mute-btn'); if (muteBtn) { threadId = muteBtn.getAttribute('data-thread-id'); } } // If no thread ID from panel, try to get from widget if (!threadId) { const widgetEl = inputEl.closest('.ext-discussiontools-ui-replyWidget') || inputEl.closest('.ext-discussiontools-ui-newTopicWidget'); if (widgetEl) { threadId = getThreadIdForWidget(widgetEl); } } // If muted, clear background and return if (threadId && isThreadMuted(threadId)) { inputEl.style.setProperty('background-color', 'transparent', 'important'); return; } let inputBg = 'transparent'; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; inputBg = TENSION_COLORS[level].inputBg; } else if (scoreChange < -SCORE_CHANGE_THRESH) { inputBg = 'rgba(76,175,80,0.05)'; } inputEl.style.setProperty('background-color', inputBg, 'important'); inputEl.style.setProperty('transition', 'background-color 0.3s ease', 'important'); } const box = document.getElementById(`${replyId}_d`); const p = document.getElementById(`${replyId}_p`); const h = document.getElementById(`${replyId}_h`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) p.textContent = text; if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary`; } } } function getContextStringFor(widgetEl) { try { console.log(`[${NAME}] === Extracting context for widget ===`); // Helper to check if element is a comment container const isCommentNode = (el) => !!el && (el.tagName === 'DD' || el.tagName === 'LI' || el.tagName === 'P'); // Extract clean comment text, removing nested replies and UI elements // IMPORTANT: Keep timestamps (UTC) because backend splits on them! const extractCommentText = (el) => { if (!el) return ''; const clone = el.cloneNode(true); // Remove nested discussion lists, widgets, and UI elements // NOTE: DO NOT remove .ext-discussiontools-init-timestamplink - backend needs timestamps to split utterances! clone.querySelectorAll('dl, .ext-discussiontools-ui-replyWidget, .craftDisplay, .passiveModeDisplay, .ext-discussiontools-init-replylink-buttons, [role="button"]').forEach(n => n.remove()); const text = (clone.innerText || '').trim(); return text; }; // Wikipedia Talk pages have structure: <dd>comment text<dl><dd>reply<dl><dd>widget // When widget appears, it's nested inside a new <dd> inside a new <dl> // We need to find the parent <dd> that contains actual comment text let commentDD = null; // Strategy 1: Widget is inside <dd> → <dl> → <dd> (the target comment) // Find the <dd> containing the widget const widgetDD = widgetEl.closest('dd, li'); console.log(`[${NAME}] Widget container DD:`, widgetDD ? widgetDD.tagName : 'not found'); if (widgetDD) { // Check if this DD has meaningful text (not just the widget) const widgetDDText = extractCommentText(widgetDD); console.log(`[${NAME}] Widget DD text length:`, widgetDDText.length); if (widgetDDText.length > 20) { // This DD has actual comment text commentDD = widgetDD; } else { // This DD is empty/minimal - the widget was just inserted // Go up: DD → DL → parent DD (which should have the comment) const parentDL = widgetDD.parentElement; console.log(`[${NAME}] Parent DL:`, parentDL ? parentDL.tagName : 'not found'); if (parentDL && (parentDL.tagName === 'DL' || parentDL.tagName === 'UL' || parentDL.tagName === 'OL')) { // Find the parent DD/LI/P that contains this DL commentDD = parentDL.closest('dd, li, p'); console.log(`[${NAME}] Found parent comment node:`, commentDD ? commentDD.tagName : 'not found'); } } } // Strategy 2: Look for preceding paragraph with comment markers (flat structure) if (!commentDD) { console.log(`[${NAME}] Trying flat structure search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 10) { probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null); if (probe && probe.tagName === 'P' && probe.querySelector('[data-mw-comment-start], [data-mw-comment-end]')) { commentDD = probe; console.log(`[${NAME}] Found comment via flat search at hop ${hops}`); break; } } } // Strategy 3: Last resort - traverse DOM looking for any comment node if (!commentDD) { console.log(`[${NAME}] Trying fallback search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 20) { if (isCommentNode(probe) && extractCommentText(probe).length > 20) { commentDD = probe; console.log(`[${NAME}] Found comment via fallback at hop ${hops}`); break; } probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null) || (probe.parentElement ? probe.parentElement.parentElement : null); } } if (!commentDD) { console.warn(`[${NAME}] Could not find any comment node!`); return ''; } // Now traverse up the comment chain collecting all parent comments const texts = []; const seen = new Set(); let current = commentDD; let iterations = 0; console.log(`[${NAME}] Starting comment chain traversal from:`, current.tagName); while (current && iterations++ < 20) { if (seen.has(current)) { console.log(`[${NAME}] Already seen this node, breaking`); break; } seen.add(current); const text = extractCommentText(current); if (text && text.length > 0) { console.log(`[${NAME}] Extracted comment (${text.length} chars):`, text.substring(0, 100) + '...'); texts.unshift(text); // Add to beginning to maintain chronological order } // Navigate up the discussion tree // Structure: <p>text</p><dl><dd>reply<dl><dd>reply</dd></dl></dd></dl> // From current <dd>, go to parent element (should be <dl>) const parentList = current.parentElement; if (!parentList) { console.log(`[${NAME}] No parent element, stopping`); break; } console.log(`[${NAME}] Parent list:`, parentList.tagName); // From <dl>, find the parent <dd> or <li> or <p> if (parentList.tagName === 'DL' || parentList.tagName === 'UL' || parentList.tagName === 'OL') { // First try to find an ancestor comment node current = parentList.closest('dd, li, p'); if (current) { console.log(`[${NAME}] Found ancestor comment:`, current.tagName); } else { // No ancestor found - the <p> might be a SIBLING of this <dl> // This happens in flat structure: <p>comment</p><dl><dd>reply... console.log(`[${NAME}] No ancestor, searching for previous sibling of DL...`); let sibling = parentList.previousElementSibling; let siblingHops = 0; while (sibling && siblingHops++ < 10) { console.log(`[${NAME}] Checking sibling:`, sibling.tagName); if (isCommentNode(sibling) && !seen.has(sibling)) { const siblingText = extractCommentText(sibling); if (siblingText.length > 20) { current = sibling; console.log(`[${NAME}] Found sibling comment with text (${siblingText.length} chars)`); break; } } sibling = sibling.previousElementSibling; } if (!current || current === parentList.closest('dd, li, p')) { console.log(`[${NAME}] No more comments found, stopping`); break; } } } else if (isCommentNode(parentList)) { // Parent is itself a comment node current = parentList; } else { // Try to find previous sibling comment let sibling = current.previousElementSibling; while (sibling && !isCommentNode(sibling)) { sibling = sibling.previousElementSibling; } if (sibling && !seen.has(sibling)) { current = sibling; console.log(`[${NAME}] Moving to sibling:`, current.tagName); } else { console.log(`[${NAME}] No more siblings, stopping`); break; } } } console.log(`[${NAME}] Extracted ${texts.length} comments, total length: ${texts.join('\n\n').length}`); // Additional pass: Look for any <p> elements with comment markers that might have been missed // This catches the case where the first comment in a thread is a <p> that's a sibling of the DL if (commentDD && commentDD.tagName === 'DD') { const topDL = commentDD.closest('dl'); if (topDL && topDL.previousElementSibling) { let probe = topDL.previousElementSibling; let probeHops = 0; console.log(`[${NAME}] Scanning backwards from top DL for more context...`); while (probe && probeHops++ < 5) { if (probe.tagName === 'P' && !seen.has(probe)) { const probeText = extractCommentText(probe); if (probeText.length > 20 && (probe.querySelector('[data-mw-comment-start]') || probe.querySelector('[data-mw-comment-end]'))) { console.log(`[${NAME}] Found additional preceding comment (${probeText.length} chars):`, probeText.substring(0, 100) + '...'); texts.unshift(probeText); seen.add(probe); } } probe = probe.previousElementSibling; } } } console.log(`[${NAME}] Final extraction: ${texts.length} comments, total length: ${texts.join('\n\n').length}`); if (texts.length > 0) { const fullContext = texts.join('\n\n'); console.log(`[${NAME}] Full context:`, fullContext); return fullContext; } console.warn(`[${NAME}] No context found, returning empty string`); return ''; } catch (err) { console.error(`[${NAME}] Error extracting context:`, err); return ''; } } async function startForWidget(widgetEl) { if (!TOKEN) { showTokenForm(widgetEl); return; } if (widgetState.has(widgetEl) || widgetEl.getAttribute('data-convowizard-processing') === 'true') return; widgetEl.setAttribute('data-convowizard-processing', 'true'); console.log(`[${NAME}] Starting widget processing:`, widgetEl.className); function findInput(scope) { // Prefer DT textbox role, then VE contenteditable surface, then textarea return ( scope.querySelector('div[role="textbox"]') || scope.querySelector('[contenteditable="true"]') || scope.querySelector('.ve-ce-surface [contenteditable="true"]') || scope.querySelector('.ve-ce-surface') || scope.querySelector('textarea') ); } async function waitForInput(scope, timeoutMs = 8000) { const deadline = Date.now() + timeoutMs; return new Promise((resolve) => { const tick = () => { const el = findInput(scope) || findInput(document); if (el) return resolve(el); if (Date.now() > deadline) return resolve(null); setTimeout(tick, 100); }; tick(); }); } const inputEl = await waitForInput(widgetEl); if (!inputEl) { console.warn(`[${NAME}] No input element found in widget (after waiting)`); return; } console.log(`[${NAME}] Input element found:`, inputEl.tagName, inputEl.getAttribute('role') || (inputEl.getAttribute('contenteditable') ? 'contenteditable' : '')); const context = getContextStringFor(widgetEl); console.log(`[${NAME}] Context extracted, length: ${context.length} chars`); console.log(`[${NAME}] Context preview:`, context.substring(0, 200)); // Split context on (UTC) timestamps to create separate utterances like Reddit does // This prevents backend caching bug where all Wikipedia convos share id=None const utterances = context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = utterances.map((text, index) => ({ id: `wiki_${Date.now()}_${index}`, // Unique ID prevents cache collision text: text.trim() })); console.log(`[${NAME}] Created ${existing.length} utterances with unique IDs`); existing.forEach((utt, i) => { console.log(`[${NAME}] ${i}: ID=${utt.id}, text=${utt.text.substring(0, 60)}...`); }); const data = await postJson('start', { existing, // Send as array like Reddit, not context string! reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); console.log(`[${NAME}] Received response from /start:`, data); // Handle invalid token - clear and show auth form if (data && data.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } if (!data || !data.interaction_id) return; widgetState.set(widgetEl, { interactionId: data.interaction_id, inputEl, context }); handleIntervention(widgetEl, inputEl, data); const sendContinue = throttle(async () => { const st = widgetState.get(widgetEl); if (!st) return; // Check if thread is muted const threadId = getThreadIdForWidget(widgetEl); if (threadId && isThreadMuted(threadId)) { // Clear input background if muted if (st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } return; } const widgetId = widgetEl.id; const h = document.getElementById(`reply_${widgetId}_h`); if (h) { // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = ` <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg> `; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span with processing indicator const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary (Processing...)`; } } // Recreate existing array from cached context for continue requests const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, // Use interaction_id for consistency text: text.trim() })); const data2 = await postJson('continue', { existing: existingForContinue, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); // Handle invalid token on continue if (data2 && data2.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } handleIntervention(widgetEl, st.inputEl, data2); }, 3000); inputEl.addEventListener('input', sendContinue); inputEl.addEventListener('keyup', sendContinue); inputEl.addEventListener('paste', sendContinue); let postBtn = widgetEl.querySelector(".oo-ui-buttonElement-button[title*='Reply']"); if (!postBtn) { postBtn = Array.from(widgetEl.querySelectorAll('.oo-ui-buttonElement-button')) .find(b => /reply/i.test((b.title || b.innerText || '').trim())); } if (postBtn) { postBtn.addEventListener('click', async () => { const st = widgetState.get(widgetEl); if (!st) return; try { // Recreate existing array from cached context for submit const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForSubmit = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); await postJson('submit', { existing: existingForSubmit, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, submitted_id: null, token: TOKEN, username: USERNAME }); } finally { widgetState.delete(widgetEl); } }); } } function handleIntervention(widgetEl, inputEl, data) { if (!data) { console.warn(`[${NAME}] handleIntervention called with no data`); return; } console.log(`[${NAME}] handleIntervention called with data:`, data); if (data.error) { return; } const interactionId = data.interaction_id; if (!interactionId) { console.warn(`[${NAME}] No interaction_id in data`); return; } const threadId = getThreadIdForWidget(widgetEl); // Skip display if thread is muted if (threadId && isThreadMuted(threadId)) { const widgetId = widgetEl.id || `widget_${Date.now()}`; if (!widgetEl.id) widgetEl.id = widgetId; ensureUnmuteIndicator(widgetEl, threadId, widgetId); return; } const { replyId, contextId } = ensurePanels(widgetEl, threadId); const showScores = Object.prototype.hasOwnProperty.call(data, 'show_scores'); console.log(`[${NAME}] Panel IDs - context: ${contextId}, reply: ${replyId}`); console.log(`[${NAME}] Show scores: ${showScores}`); const which = (data.which || '').substring(0, 5); console.log(`[${NAME}] Response type (which): ${which}`); if (which === 'craft') { console.log(`[${NAME}] CRAFT mode - craft_ctx_score:`, data.craft_ctx_score); console.log(`[${NAME}] CRAFT mode - craft_reply_score:`, data.craft_reply_score); console.log(`[${NAME}] CRAFT mode - craft_reply_change:`, data.craft_reply_change); if (data.craft_ctx_score != null) { console.log(`[${NAME}] Updating context panel with score: ${data.craft_ctx_score}`); updateContextPanel(contextId, data.craft_ctx_score, showScores); } else { console.log(`[${NAME}] No context score, updating context panel with null`); updateContextPanel(contextId, null, showScores); } if (data.craft_reply_change != null && data.craft_reply_score != null) { console.log(`[${NAME}] Updating reply panel with change: ${data.craft_reply_change}, score: ${data.craft_reply_score}`); updateReplyPanel(replyId, data.craft_reply_change, data.craft_reply_score, showScores, inputEl); } else { console.log(`[${NAME}] No reply scores, updating reply panel with zeros`); updateReplyPanel(replyId, 0, 0, showScores, inputEl); } } else { const noteId = `passivenote${interactionId}_d`; if (!data.message && !document.getElementById(noteId)) { const box = document.createElement('div'); box.id = noteId; box.className = 'passiveModeDisplay'; box.style.cssText = ` margin:8px 0;padding:8px 12px;border-radius:6px;border:1px solid #a2a9b1; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;opacity:.8; `; const content = document.createElement('div'); content.id = `passivenote${interactionId}_p`; content.style.cssText = `font-size:12px;color:#54595d;margin:0;font-style:italic;`; content.textContent = `${NAME} is currently not active on this thread.`; box.appendChild(content); widgetEl.prepend(box); } } } function installClickDelegation() { document.addEventListener('click', (e) => { console.log(`[${NAME}] Click detected on:`, e.target.tagName, e.target.className); // Match reply buttons and links (both old and new DiscussionTools formats) // Need to check the actual clicked element AND traverse up const link = e.target.closest && ( e.target.closest('.ext-discussiontools-init-replybutton') || e.target.closest('.ext-discussiontools-init-replylink-reply') || e.target.closest('a.ext-discussiontools-init-replylink') || e.target.closest('.ext-discussiontools-init-replylink-buttons') || e.target.closest('[class*="ext-discussiontools-init-"][class*="reply"]') ); if (!link) return; console.log(`[${NAME}] Reply link clicked:`, link.className); // For flat discussion structures (like Research talk pages), the widget may be inserted // outside the immediate parent. Search more broadly. const anchorScope = link.closest('dd, li, p, div, section') || document.body; const broadScope = anchorScope.closest('#mw-content-text, .mw-parser-output, #content') || document.body; console.log(`[${NAME}] Anchor scope:`, anchorScope.tagName, anchorScope.className); console.log(`[${NAME}] Broad scope:`, broadScope.tagName, broadScope.className); const timeoutAt = Date.now() + 8000; let attempts = 0; const poll = setInterval(() => { attempts++; // Try multiple search strategies: // 1. In immediate anchor scope (for nested structures like dd/li) // 2. As sibling to anchor scope (for flat structures like p) // 3. In broader content scope // 4. Document-wide fallback const widget = anchorScope.querySelector('.ext-discussiontools-ui-replyWidget') || anchorScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || (anchorScope.nextElementSibling && ( anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-replyWidget') || anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-newTopicWidget') ) ? anchorScope.nextElementSibling : null) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-replyWidget:last-child')) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-newTopicWidget:last-child')) || broadScope.querySelector('.ext-discussiontools-ui-replyWidget') || broadScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || document.querySelector('.ext-discussiontools-ui-replyWidget') || document.querySelector('.ext-discussiontools-ui-newTopicWidget'); if (widget) { console.log(`[${NAME}] Widget found after ${attempts} attempts:`, widget.className); clearInterval(poll); startForWidget(widget); } else if (Date.now() > timeoutAt) { console.log(`[${NAME}] Widget timeout after ${attempts} attempts - no widget found`); console.log(`[${NAME}] Searched in anchorScope:`, anchorScope); console.log(`[${NAME}] Searched in broadScope:`, broadScope); clearInterval(poll); } else if (attempts % 10 === 0) { console.log(`[${NAME}] Still searching for widget, attempt ${attempts}...`); } }, 100); }); } function installWidgetObserver() { const root = document.getElementById('content') || document.body; console.log(`[${NAME}] Setting up mutation observer on:`, root.id || root.tagName); const obs = new MutationObserver((mutations) => { for (const m of mutations) { for (const n of m.addedNodes) { if (!(n instanceof HTMLElement)) continue; // Log all significant additions for debugging if (n.className && typeof n.className === 'string' && n.className.includes('ext-discussiontools')) { console.log(`[${NAME}] DOM addition detected:`, n.tagName, n.className); } // Match specific DiscussionTools widgets if (n.matches('.ext-discussiontools-ui-replyWidget') || n.matches('.ext-discussiontools-ui-newTopicWidget')) { console.log(`[${NAME}] *** Widget detected via observer ***:`, n.className); startForWidget(n); } else { const nested = n.querySelector && ( n.querySelector('.ext-discussiontools-ui-replyWidget') || n.querySelector('.ext-discussiontools-ui-newTopicWidget') ); if (nested) { console.log(`[${NAME}] *** Nested widget detected ***:`, nested.className); startForWidget(nested); } } } } }); obs.observe(root, { childList: true, subtree: true }); console.log(`[${NAME}] Mutation observer is now watching for widget creation`); // Check for existing widgets on page load const existingWidgets = document.querySelectorAll('.ext-discussiontools-ui-replyWidget, .ext-discussiontools-ui-newTopicWidget'); if (existingWidgets.length > 0) { console.log(`[${NAME}] Found ${existingWidgets.length} existing widget(s) on page load`); existingWidgets.forEach(w => { console.log(`[${NAME}] Processing existing widget:`, w.className); startForWidget(w); }); } else { console.log(`[${NAME}] No existing widgets found on page load`); } } // kick off installClickDelegation(); installWidgetObserver(); console.log(`[${NAME}] === INITIALIZATION COMPLETE ===`); console.log(`[${NAME}] Click delegation: ACTIVE`); console.log(`[${NAME}] Mutation observer: ACTIVE`); console.log(`[${NAME}] Waiting for user interaction...`); })(); //</nowiki> f7z3p0vdps3xx2fqnhz1ga1d18cc8lh 739812 739809 2026-04-30T01:13:14Z Leojqian 71700 739812 javascript text/javascript //<nowiki> /** * Filename: wiki-talk-page-script.js * ConvoWizard – Real-time Reply Feedback (Wikipedia userscript) * Runs on Talk pages inside DiscussionTools reply widgets. * Prompts for a token on first run; persists after authorization. */ (async function () { 'use strict'; // Load core util module via ResourceLoader await mw.loader.using(['mediawiki.util', 'mediawiki.user', 'mediawiki.api']); // === CONFIG === const SERVER = 'https://convocompass.toolforge.org/api/'; // ---- UI strings and thresholds ---- const NAME = 'ConvoWizard'; const DEBUG = /(?:\?|&)convowizardDebug=1(?:&|$)/.test(location.search); const OPTION_KEY = 'userjs-convowizard-token'; const SCORE_CHANGE_THRESH = 0.08; const MID_TENSION_THRESH = 0.50; const HIGH_TENSION_THRESH = 0.75; const CONTEXT_LOW = `${NAME} will notify you here if it detects anything in the preceding conversation.`; const CONTEXT_MID = `${NAME} thinks this discussion is getting somewhat tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_HIGH = `${NAME} thinks this discussion is getting tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const CONTEXT_VERY_HIGH = `${NAME} thinks this discussion is getting very tense - some other discussions that started like this one ended up with comments getting removed. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const REPLY_LOW = `${NAME} thinks this comment might decrease tension in this discussion.`; const REPLY_MID = `${NAME} will notify you here if it detects anything in your comment draft.`; const REPLY_HIGH = `${NAME} thinks this comment might increase the tension in this discussion. Remember that you will be most likely to have a productive discussion with a civil, respectful, and open approach.`; const TENSION_COLORS = { mid: { border: '#e8825c', bg: 'linear-gradient(135deg, #fff0eb 0%, #fff8f5 100%)', icon: '#e8825c', inputBg: 'rgba(232,130,92,0.05)' }, high: { border: '#d73027', bg: 'linear-gradient(135deg, #ffeaea 0%, #fff5f5 100%)', icon: '#d73027', inputBg: 'rgba(215,48,39,0.05)' }, veryHigh: { border: '#a50f15', bg: 'linear-gradient(135deg, #fdd 0%, #fee 100%)', icon: '#a50f15', inputBg: 'rgba(165,15,21,0.07)' }, }; // Keep ConvoWizard logs quiet by default to reduce console noise on Wikimedia pages. const nativeConsole = window.console; const console = { log: (...args) => { if (!DEBUG && typeof args[0] === 'string' && args[0].startsWith(`[${NAME}]`)) { return; } nativeConsole.log(...args); }, warn: (...args) => nativeConsole.warn(...args), error: (...args) => nativeConsole.error(...args) }; const isWikipediaHost = /(^|\.)wikipedia\.org$/.test(location.hostname); const namespaceNumber = Number(mw.config.get('wgNamespaceNumber')); const isTalkNamespace = Number.isFinite(namespaceNumber) && namespaceNumber % 2 === 1; const isViewAction = mw.config.get('wgAction') === 'view'; // Gadget scope guard: only run on Wikipedia talk pages in normal view mode. if (!isWikipediaHost || !isTalkNamespace || !isViewAction) { console.log( `[${NAME}] Skipping page (host=${location.hostname}, ns=${namespaceNumber}, action=${mw.config.get('wgAction')})` ); return; } // ---------- helpers ---------- function throttle(fn, waitMs) { let last = 0, timer = 0, pendingArgs = null; return (...args) => { const now = Date.now(); const remaining = waitMs - (now - last); if (remaining <= 0) { last = now; fn(...args); } else { pendingArgs = args; clearTimeout(timer); timer = setTimeout(() => { last = Date.now(); fn(...pendingArgs); }, remaining); } }; } // Rewrite Wikipedia URL so the reddit-style backend can extract a post_id. // Backend does: url.split("comments/")[1][:6] // We inject "comments/<hash>" where hash is derived from the wiki page path. function wikiUrlToPostId(href) { try { const pagePath = href.split('/wiki/')[1] || href; // Simple hash to produce a stable 6-char hex id per page let h = 0; for (let i = 0; i < pagePath.length; i++) { h = ((h << 5) - h) + pagePath.charCodeAt(i); h = h & h; } const hex = Math.abs(h).toString(16).padStart(6, '0').slice(0, 6); return href.split('/wiki/')[0] + '/comments/' + hex; } catch { return href; } } async function postJson(route, body) { try { const res = await fetch(SERVER + route, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const text = await res.text(); try { return JSON.parse(text); } catch { return {}; } } catch { return {}; } } function getTokenFromOptions() { try { if (mw.user && mw.user.options && typeof mw.user.options.get === 'function') { const raw = mw.user.options.get(OPTION_KEY); if (typeof raw === 'string') { const trimmed = raw.trim(); return trimmed.length > 0 ? trimmed : null; } } } catch (err) { console.warn(`[${NAME}] Failed to read token from user options`, err); } return null; } async function persistToken(storageKey, token) { if (!token) { localStorage.removeItem(storageKey); } else { localStorage.setItem(storageKey, token); } if (!(mw.user && mw.user.options && typeof mw.user.options.set === 'function')) { return; } try { mw.user.options.set(OPTION_KEY, token || ''); await new mw.Api().saveOption(OPTION_KEY, token || ''); console.log(`[${NAME}] ${token ? 'Saved' : 'Cleared'} token in user options (notepad)`); } catch (err) { console.warn(`[${NAME}] Could not persist token to user options`, err); } } async function validateToken(token, username) { try { const response = await postJson('claim_token', { token, username }); return response.valid === true; } catch { return false; } } // ========== CREDENTIAL SETUP ========== const USERNAME = mw.config.get('wgUserName'); if (!USERNAME) { console.log(`[${NAME}] Not logged in, skipping`); return; } let TOKEN = null; const STORAGE_KEY = `ConvoWizard:token:${USERNAME}`; // Load persisted token (options → localStorage), validate, sync stores async function loadPersistedToken() { let token = getTokenFromOptions(); let source = 'options'; if (!token) { token = localStorage.getItem(STORAGE_KEY); source = 'localStorage'; } if (token && typeof token === 'string') { token = token.trim(); } if (!token) return; const valid = await validateToken(token, USERNAME); if (valid) { TOKEN = token; // Sync both stores if (source === 'options') { localStorage.setItem(STORAGE_KEY, token); } else { await persistToken(STORAGE_KEY, token); } console.log(`[${NAME}] Token loaded from ${source}`); } else { console.log(`[${NAME}] Persisted token invalid, clearing`); await persistToken(STORAGE_KEY, null); } } await loadPersistedToken(); console.log(`[${NAME}] === SCRIPT LOADED SUCCESSFULLY ===`); console.log(`[${NAME}] Server: ${SERVER}`); console.log(`[${NAME}] User: ${USERNAME}`); console.log(`[${NAME}] Token: ${TOKEN ? 'present' : 'none (will prompt)'}`); console.log(`[${NAME}] Page URL: ${location.href}`); console.log(`[${NAME}] Installing click and mutation observers...`); // ========== TOKEN INPUT FORM ========== function showTokenForm(widgetEl) { // Don't add duplicate forms if (widgetEl.querySelector('.convowizard-auth-form')) return; const form = document.createElement('div'); form.className = 'convowizard-auth-form'; form.style.cssText = ` margin:8px 0;padding:16px 20px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; const title = document.createElement('div'); title.style.cssText = 'font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;'; title.textContent = `${NAME}: Authorization Required`; const desc = document.createElement('div'); desc.style.cssText = 'font-size:13px;color:#333;margin-bottom:12px;line-height:1.4;'; desc.textContent = 'Please paste your ConvoWizard token below to activate the extension.'; const row = document.createElement('div'); row.style.cssText = 'display:flex;gap:8px;align-items:center;'; const input = document.createElement('input'); input.type = 'password'; input.placeholder = 'Paste token here'; input.autocomplete = 'off'; input.spellcheck = false; input.style.cssText = ` flex:1;padding:8px 10px;font-size:13px;border:1px solid #a2a9b1;border-radius:4px; font-family:monospace;outline:none;transition:border-color 0.2s ease; `; const btn = document.createElement('button'); btn.type = 'button'; btn.textContent = 'Authorize'; btn.style.cssText = ` padding:8px 16px;font-size:13px;font-weight:600;border:none;border-radius:4px; background:#0645ad;color:#fff;cursor:pointer;transition:background 0.2s ease; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; `; btn.addEventListener('mouseenter', () => { btn.style.background = '#0b5cce'; }); btn.addEventListener('mouseleave', () => { btn.style.background = '#0645ad'; }); const errDiv = document.createElement('div'); errDiv.style.cssText = 'font-size:12px;color:#d73027;margin-top:8px;display:none;'; async function onAuthorize() { const raw = input.value.trim(); // Input validation: 33 hex chars if (!/^[0-9a-f]{33}$/i.test(raw)) { errDiv.textContent = 'Invalid token format. A token is 33 hexadecimal characters.'; errDiv.style.display = 'block'; input.value = ''; input.focus(); return; } btn.disabled = true; btn.textContent = 'Validating...'; errDiv.style.display = 'none'; const valid = await validateToken(raw, USERNAME); // Clear input immediately after use input.value = ''; if (valid) { TOKEN = raw; await persistToken(STORAGE_KEY, TOKEN); // Remove all auth forms on page document.querySelectorAll('.convowizard-auth-form').forEach(f => f.remove()); // Activate the widget startForWidget(widgetEl); } else { errDiv.textContent = 'Token is invalid or expired. Please check and try again.'; errDiv.style.display = 'block'; btn.disabled = false; btn.textContent = 'Authorize'; input.focus(); } } btn.addEventListener('click', onAuthorize); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); onAuthorize(); } }); row.appendChild(input); row.appendChild(btn); form.appendChild(title); form.appendChild(desc); form.appendChild(row); form.appendChild(errDiv); widgetEl.prepend(form); input.focus(); } // ---- state + logs ---- const widgetState = new WeakMap(); function cleanupDuplicatePanels() { const allPanels = document.querySelectorAll('.craftDisplay, .passiveModeDisplay'); const seen = new Set(); allPanels.forEach(panel => { const pid = panel.id; if (pid && pid.includes('_')) { const parts = pid.split('_'); const type = parts[0]; const wid = parts.slice(1).join('_').replace('_d', ''); const key = `${type}|${wid}`; if (seen.has(key)) panel.remove(); else seen.add(key); } }); } // ========== MUTE THREAD ========== /** * Generates a unique thread identifier by combining URL section, DOM position, and content hash. * Ensures each thread on a page gets a distinct ID for per-thread mute functionality. */ function getThreadIdForWidget(widgetEl) { try { const urlHash = location.hash ? location.hash.replace('#', '') : 'root'; // Generate DOM path for widget position const widgetPath = []; let el = widgetEl; for (let i = 0; i < 8 && el; i++) { const parent = el.parentElement; if (!parent || parent === document.body) break; const siblings = Array.from(parent.children); const index = siblings.indexOf(el); widgetPath.unshift(`${parent.tagName}_${index}`); el = parent; } const domPath = widgetPath.length > 0 ? widgetPath.join('_') : 'unknown'; // Get content-based hash from first comment const context = getContextStringFor(widgetEl); let contentHash = ''; if (context && context.length > 0) { const firstComment = context.split('\n\n')[0] || context.substring(0, 150); let hash = 0; for (let i = 0; i < Math.min(firstComment.length, 150); i++) { const char = firstComment.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { const parentText = widgetEl.parentElement ? (widgetEl.parentElement.textContent || '').substring(0, 50).trim() : ''; if (parentText) { let hash = 0; for (let i = 0; i < parentText.length; i++) { hash = ((hash << 5) - hash) + parentText.charCodeAt(i); hash = hash & hash; } contentHash = Math.abs(hash).toString(36); } else { contentHash = Math.random().toString(36).slice(2, 8); } } return `thread_${urlHash}_${domPath.substring(0, 50)}_${contentHash}`; } catch (err) { console.warn(`[${NAME}] Error generating thread ID:`, err); return `thread_${Date.now()}_${Math.random().toString(36).slice(2, 12)}`; } } function getMutedThreads() { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const stored = localStorage.getItem(storageKey); if (!stored) return new Set(); const threads = JSON.parse(stored); return new Set(Array.isArray(threads) ? threads : []); } catch { return new Set(); } } function saveMutedThreads(threadSet) { try { const storageKey = `ConvoWizard:mutedThreads:${USERNAME}`; const threads = Array.from(threadSet); localStorage.setItem(storageKey, JSON.stringify(threads)); } catch (err) { console.warn(`[${NAME}] Error saving muted threads:`, err); } } function isThreadMuted(threadId) { if (!threadId) return false; const muted = getMutedThreads(); return muted.has(threadId); } function toggleThreadMute(threadId) { if (!threadId) return false; const muted = getMutedThreads(); const wasMuted = muted.has(threadId); if (wasMuted) { muted.delete(threadId); } else { muted.add(threadId); } saveMutedThreads(muted); return !wasMuted; } /** * Creates an unmute indicator button that appears when a thread is muted * Allows users to restore ConvoWizard for a muted thread */ function ensureUnmuteIndicator(widgetEl, threadId, widgetId) { const existingIndicator = widgetEl.querySelector('.convowizard-unmute-indicator'); if (existingIndicator) return; const indicator = document.createElement('div'); indicator.id = `convowizard-unmute-${widgetId}`; indicator.className = 'convowizard-unmute-indicator'; indicator.style.cssText = ` margin: 4px 0; padding: 6px 10px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: #f8f9fa; color: #54595d; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: flex; align-items: center; gap: 6px; cursor: pointer; transition: all 0.2s ease; `; const icon = document.createElement('span'); icon.textContent = '🔊'; icon.style.fontSize = '12px'; const text = document.createElement('span'); text.textContent = `${NAME} is muted for this thread. Click to unmute.`; text.style.flex = '1'; indicator.appendChild(icon); indicator.appendChild(text); indicator.addEventListener('mouseenter', () => { indicator.style.background = '#e9ecef'; indicator.style.borderColor = '#0645ad'; }); indicator.addEventListener('mouseleave', () => { indicator.style.background = '#f8f9fa'; indicator.style.borderColor = '#a2a9b1'; }); indicator.addEventListener('click', async (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Show panels and remove indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); if (contextPanel) contextPanel.style.display = ''; if (replyPanel) replyPanel.style.display = ''; widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); // Update mute buttons document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(btn => { btn.textContent = '🔇 Mute thread'; btn.title = 'Mute ConvoWizard for this thread'; btn.style.background = '#ffffff'; }); // Re-trigger analysis to refresh display const st = widgetState.get(widgetEl); if (st && st.inputEl) { // Re-run the continue request to refresh the display const inputText = st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''; if (inputText.trim().length > 0) { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('continue', { existing: existingForContinue, new: inputText, interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); if (data && !data.error) { handleIntervention(widgetEl, st.inputEl, data); } } else { const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); const data = await postJson('start', { existing, reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); if (data && !data.error && data.interaction_id) { widgetState.set(widgetEl, { ...st, interactionId: data.interaction_id }); handleIntervention(widgetEl, st.inputEl, data); } } } else { startForWidget(widgetEl); } }); // Insert at the beginning of the widget widgetEl.insertBefore(indicator, widgetEl.firstChild); } function createMuteButton(threadId, widgetId, isMuted) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'convowizard-mute-btn'; btn.setAttribute('data-thread-id', threadId); btn.setAttribute('data-widget-id', widgetId); btn.style.cssText = ` margin-left: auto; padding: 4px 8px; font-size: 11px; border: 1px solid #a2a9b1; border-radius: 4px; background: ${isMuted ? '#f8f9fa' : '#ffffff'}; color: #54595d; cursor: pointer; transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; btn.textContent = isMuted ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = isMuted ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.addEventListener('mouseenter', () => { btn.style.background = isMuted ? '#e9ecef' : '#f8f9fa'; }); btn.addEventListener('mouseleave', () => { btn.style.background = isMuted ? '#f8f9fa' : '#ffffff'; }); btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const newMuteState = toggleThreadMute(threadId); // Update button state btn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; btn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; btn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; // Hide/show panels and unmute indicator const contextPanel = document.getElementById(`context_${widgetId}_d`); const replyPanel = document.getElementById(`reply_${widgetId}_d`); const widgetEl = contextPanel?.closest('.ext-discussiontools-ui-replyWidget') || replyPanel?.closest('.ext-discussiontools-ui-replyWidget') || contextPanel?.closest('.ext-discussiontools-ui-newTopicWidget') || replyPanel?.closest('.ext-discussiontools-ui-newTopicWidget'); if (contextPanel) contextPanel.style.display = newMuteState ? 'none' : ''; if (replyPanel) replyPanel.style.display = newMuteState ? 'none' : ''; // Clear textbox background when muting if (newMuteState) { const st = widgetState.get(widgetEl); if (st && st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } } if (newMuteState && widgetEl) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (widgetEl) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Update all mute buttons for this thread document.querySelectorAll(`button[data-thread-id="${threadId}"]`).forEach(otherBtn => { if (otherBtn !== btn) { otherBtn.textContent = newMuteState ? '🔊 Unmute thread' : '🔇 Mute thread'; otherBtn.title = newMuteState ? 'Unmute ConvoWizard for this thread' : 'Mute ConvoWizard for this thread'; otherBtn.style.background = newMuteState ? '#f8f9fa' : '#ffffff'; } }); }); return btn; } function ensurePanels(widgetEl, threadId = null) { cleanupDuplicatePanels(); // Clean up duplicate unmute indicators const unmuteIndicators = widgetEl.querySelectorAll('.convowizard-unmute-indicator'); if (unmuteIndicators.length > 1) { for (let i = 1; i < unmuteIndicators.length; i++) { unmuteIndicators[i].remove(); } } const widgetId = widgetEl.id || `widget_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`; if (!widgetEl.id) widgetEl.id = widgetId; const contextId = `context_${widgetId}`; const replyId = `reply_${widgetId}`; const isMuted = threadId ? isThreadMuted(threadId) : false; // Ensure Context panel if (!document.getElementById(`${contextId}_d`)) { const contextBox = document.createElement('div'); contextBox.id = `${contextId}_d`; contextBox.className = 'craftDisplay'; contextBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const cheader = document.createElement('div'); cheader.id = `${contextId}_h`; cheader.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Context Summary`; cheader.appendChild(iconSvg); cheader.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); cheader.appendChild(muteBtn); } const ccontent = document.createElement('div'); ccontent.id = `${contextId}_p`; ccontent.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; ccontent.textContent = `When you type a reply ${NAME} will give you some feedback on the discussion context.`; contextBox.appendChild(cheader); contextBox.appendChild(ccontent); if (isMuted) contextBox.style.display = 'none'; widgetEl.prepend(contextBox); } else if (threadId) { const existingPanel = document.getElementById(`${contextId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } // Show unmute indicator when muted (only if panels don't exist yet) if (threadId && isMuted && !document.getElementById(`${contextId}_d`) && !document.getElementById(`${replyId}_d`)) { ensureUnmuteIndicator(widgetEl, threadId, widgetId); } else if (threadId && !isMuted) { widgetEl.querySelectorAll('.convowizard-unmute-indicator').forEach(ind => ind.remove()); } // Ensure Reply panel if (!document.getElementById(`${replyId}_d`)) { const replyBox = document.createElement('div'); replyBox.id = `${replyId}_d`; replyBox.className = 'craftDisplay'; replyBox.style.cssText = ` margin:8px 0;padding:12px 16px;border-radius:8px;border:2px solid #0645ad; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); box-shadow:0 2px 8px rgba(0,0,0,.1);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;transition:all .3s ease;position:relative; `; const header = document.createElement('div'); header.id = `${replyId}_h`; header.style.cssText = `font-weight:600;font-size:14px;color:#0645ad;margin-bottom:8px;display:flex;align-items:center;gap:8px;`; const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); iconSvg.setAttribute('width', '16'); iconSvg.setAttribute('height', '16'); iconSvg.setAttribute('viewBox', '0 0 16 16'); iconSvg.setAttribute('fill', 'currentColor'); iconSvg.innerHTML = '<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/>'; const textSpan = document.createElement('span'); textSpan.className = 'convowizard-header-text'; textSpan.textContent = `${NAME}: Reply Summary`; header.appendChild(iconSvg); header.appendChild(textSpan); if (threadId) { const muteBtn = createMuteButton(threadId, widgetId, isMuted); header.appendChild(muteBtn); } const content = document.createElement('div'); content.id = `${replyId}_p`; content.style.cssText = `font-size:13px;line-height:1.4;color:#333;margin:0;`; content.textContent = `When you type a reply ${NAME} will give you some feedback on your comment.`; replyBox.appendChild(header); replyBox.appendChild(content); if (isMuted) replyBox.style.display = 'none'; const preview = widgetEl.querySelector('.ext-discussiontools-ui-replyWidget-preview') || document.querySelector('.ext-discussiontools-ui-replyWidget-preview'); if (preview && preview.parentElement) { preview.parentElement.insertBefore(replyBox, preview.nextSibling); } else { widgetEl.append(replyBox); } } else if (threadId) { const existingPanel = document.getElementById(`${replyId}_d`); if (existingPanel) { existingPanel.style.display = isThreadMuted(threadId) ? 'none' : ''; } } return { replyId, contextId }; } function updateContextPanel(contextId, score, showScores) { console.log(`[${NAME}] updateContextPanel called - contextId: ${contextId}, score: ${score}, showScores: ${showScores}`); const hasScore = typeof score === 'number'; let text = showScores && hasScore ? `Context craft score: ${score.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; console.log(`[${NAME}] hasScore: ${hasScore}, score value: ${score}`); console.log(`[${NAME}] Thresholds - MID: ${MID_TENSION_THRESH}, HIGH: ${HIGH_TENSION_THRESH}`); if (hasScore && score > MID_TENSION_THRESH) { let level; if (score > HIGH_TENSION_THRESH) { console.log(`[${NAME}] Very high tension (score > ${HIGH_TENSION_THRESH})`); text += `${CONTEXT_VERY_HIGH}`; level = 'veryHigh'; } else if (score > 0.60) { console.log(`[${NAME}] High tension (score > 0.60)`); text += `${CONTEXT_HIGH}`; level = 'high'; } else { console.log(`[${NAME}] Mid tension (score > ${MID_TENSION_THRESH})`); text += `${CONTEXT_MID}`; level = 'mid'; } borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else { console.log(`[${NAME}] Low tension or no score`); text += `${CONTEXT_LOW}`; } const box = document.getElementById(`${contextId}_d`); const p = document.getElementById(`${contextId}_p`); const h = document.getElementById(`${contextId}_h`); console.log(`[${NAME}] DOM elements - box: ${!!box}, p: ${!!p}, h: ${!!h}`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) { p.textContent = text; console.log(`[${NAME}] Updated text content (length: ${text.length})`); } if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Context Summary`; } console.log(`[${NAME}] Updated header color: ${iconColor}`); } } function updateReplyPanel(replyId, scoreChange, rawScore, showScores, inputEl) { let text = showScores ? `Change in craft score after your comment: ${scoreChange.toFixed(4)} ` : ''; let borderColor = '#0645ad'; let bgGradient = 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'; let iconColor = '#0645ad'; let icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg>`; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { text += `${REPLY_HIGH}`; const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; borderColor = TENSION_COLORS[level].border; bgGradient = TENSION_COLORS[level].bg; iconColor = TENSION_COLORS[level].icon; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 4h2v5H7V4zm0 6h2v2H7v-2z"/> </svg>`; } else if (rawScore - scoreChange > 0.55 && scoreChange < -SCORE_CHANGE_THRESH) { text += `${REPLY_LOW}`; borderColor = '#4caf50'; bgGradient = 'linear-gradient(135deg, #f1f8e9 0%, #f9fbe7 100%)'; iconColor = '#4caf50'; icon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 6L6.5 11 4 8.5l1-1 1.5 1.5L10.5 5l1 1z"/> </svg>`; } else { text += `${REPLY_MID}`; } if (inputEl) { let threadId = null; const replyBox = document.getElementById(`${replyId}_d`); if (replyBox) { const muteBtn = replyBox.querySelector('.convowizard-mute-btn'); if (muteBtn) { threadId = muteBtn.getAttribute('data-thread-id'); } } // If no thread ID from panel, try to get from widget if (!threadId) { const widgetEl = inputEl.closest('.ext-discussiontools-ui-replyWidget') || inputEl.closest('.ext-discussiontools-ui-newTopicWidget'); if (widgetEl) { threadId = getThreadIdForWidget(widgetEl); } } // If muted, clear background and return if (threadId && isThreadMuted(threadId)) { inputEl.style.setProperty('background-color', 'transparent', 'important'); return; } let inputBg = 'transparent'; if (scoreChange > SCORE_CHANGE_THRESH || (rawScore > 0.55 && scoreChange > 0)) { const level = rawScore > HIGH_TENSION_THRESH ? 'veryHigh' : rawScore > 0.60 ? 'high' : 'mid'; inputBg = TENSION_COLORS[level].inputBg; } else if (scoreChange < -SCORE_CHANGE_THRESH) { inputBg = 'rgba(76,175,80,0.05)'; } inputEl.style.setProperty('background-color', inputBg, 'important'); inputEl.style.setProperty('transition', 'background-color 0.3s ease', 'important'); } const box = document.getElementById(`${replyId}_d`); const p = document.getElementById(`${replyId}_p`); const h = document.getElementById(`${replyId}_h`); if (box) { const muteBtn = box.querySelector('.convowizard-mute-btn'); if (muteBtn) { const threadId = muteBtn.getAttribute('data-thread-id'); if (threadId && isThreadMuted(threadId)) { box.style.display = 'none'; return; } } box.style.borderColor = borderColor; box.style.background = bgGradient; box.style.boxShadow = `0 2px 8px ${borderColor}20`; if (box.style.display === 'none' && (!muteBtn || !isThreadMuted(muteBtn.getAttribute('data-thread-id')))) { box.style.display = ''; } } if (p) p.textContent = text; if (h) { h.style.color = iconColor; // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = icon; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary`; } } } function getContextStringFor(widgetEl) { try { console.log(`[${NAME}] === Extracting context for widget ===`); // Helper to check if element is a comment container const isCommentNode = (el) => !!el && (el.tagName === 'DD' || el.tagName === 'LI' || el.tagName === 'P'); // Extract clean comment text, removing nested replies and UI elements // IMPORTANT: Keep timestamps (UTC) because backend splits on them! const extractCommentText = (el) => { if (!el) return ''; const clone = el.cloneNode(true); // Remove nested discussion lists, widgets, and UI elements // NOTE: DO NOT remove .ext-discussiontools-init-timestamplink - backend needs timestamps to split utterances! clone.querySelectorAll('dl, .ext-discussiontools-ui-replyWidget, .craftDisplay, .passiveModeDisplay, .ext-discussiontools-init-replylink-buttons, [role="button"]').forEach(n => n.remove()); const text = (clone.innerText || '').trim(); return text; }; // Wikipedia Talk pages have structure: <dd>comment text<dl><dd>reply<dl><dd>widget // When widget appears, it's nested inside a new <dd> inside a new <dl> // We need to find the parent <dd> that contains actual comment text let commentDD = null; // Strategy 1: Widget is inside <dd> → <dl> → <dd> (the target comment) // Find the <dd> containing the widget const widgetDD = widgetEl.closest('dd, li'); console.log(`[${NAME}] Widget container DD:`, widgetDD ? widgetDD.tagName : 'not found'); if (widgetDD) { // Check if this DD has meaningful text (not just the widget) const widgetDDText = extractCommentText(widgetDD); console.log(`[${NAME}] Widget DD text length:`, widgetDDText.length); if (widgetDDText.length > 20) { // This DD has actual comment text commentDD = widgetDD; } else { // This DD is empty/minimal - the widget was just inserted // Go up: DD → DL → parent DD (which should have the comment) const parentDL = widgetDD.parentElement; console.log(`[${NAME}] Parent DL:`, parentDL ? parentDL.tagName : 'not found'); if (parentDL && (parentDL.tagName === 'DL' || parentDL.tagName === 'UL' || parentDL.tagName === 'OL')) { // Find the parent DD/LI/P that contains this DL commentDD = parentDL.closest('dd, li, p'); console.log(`[${NAME}] Found parent comment node:`, commentDD ? commentDD.tagName : 'not found'); } } } // Strategy 2: Look for preceding paragraph with comment markers (flat structure) if (!commentDD) { console.log(`[${NAME}] Trying flat structure search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 10) { probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null); if (probe && probe.tagName === 'P' && probe.querySelector('[data-mw-comment-start], [data-mw-comment-end]')) { commentDD = probe; console.log(`[${NAME}] Found comment via flat search at hop ${hops}`); break; } } } // Strategy 3: Last resort - traverse DOM looking for any comment node if (!commentDD) { console.log(`[${NAME}] Trying fallback search...`); let probe = widgetEl; let hops = 0; while (probe && hops++ < 20) { if (isCommentNode(probe) && extractCommentText(probe).length > 20) { commentDD = probe; console.log(`[${NAME}] Found comment via fallback at hop ${hops}`); break; } probe = probe.previousElementSibling || (probe.parentElement ? probe.parentElement.previousElementSibling : null) || (probe.parentElement ? probe.parentElement.parentElement : null); } } if (!commentDD) { console.warn(`[${NAME}] Could not find any comment node!`); return ''; } // Now traverse up the comment chain collecting all parent comments const texts = []; const seen = new Set(); let current = commentDD; let iterations = 0; console.log(`[${NAME}] Starting comment chain traversal from:`, current.tagName); while (current && iterations++ < 20) { if (seen.has(current)) { console.log(`[${NAME}] Already seen this node, breaking`); break; } seen.add(current); const text = extractCommentText(current); if (text && text.length > 0) { console.log(`[${NAME}] Extracted comment (${text.length} chars):`, text.substring(0, 100) + '...'); texts.unshift(text); // Add to beginning to maintain chronological order } // Navigate up the discussion tree // Structure: <p>text</p><dl><dd>reply<dl><dd>reply</dd></dl></dd></dl> // From current <dd>, go to parent element (should be <dl>) const parentList = current.parentElement; if (!parentList) { console.log(`[${NAME}] No parent element, stopping`); break; } console.log(`[${NAME}] Parent list:`, parentList.tagName); // From <dl>, find the parent <dd> or <li> or <p> if (parentList.tagName === 'DL' || parentList.tagName === 'UL' || parentList.tagName === 'OL') { // First try to find an ancestor comment node current = parentList.closest('dd, li, p'); if (current) { console.log(`[${NAME}] Found ancestor comment:`, current.tagName); } else { // No ancestor found - the <p> might be a SIBLING of this <dl> // This happens in flat structure: <p>comment</p><dl><dd>reply... console.log(`[${NAME}] No ancestor, searching for previous sibling of DL...`); let sibling = parentList.previousElementSibling; let siblingHops = 0; while (sibling && siblingHops++ < 10) { console.log(`[${NAME}] Checking sibling:`, sibling.tagName); if (isCommentNode(sibling) && !seen.has(sibling)) { const siblingText = extractCommentText(sibling); if (siblingText.length > 20) { current = sibling; console.log(`[${NAME}] Found sibling comment with text (${siblingText.length} chars)`); break; } } sibling = sibling.previousElementSibling; } if (!current || current === parentList.closest('dd, li, p')) { console.log(`[${NAME}] No more comments found, stopping`); break; } } } else if (isCommentNode(parentList)) { // Parent is itself a comment node current = parentList; } else { // Try to find previous sibling comment let sibling = current.previousElementSibling; while (sibling && !isCommentNode(sibling)) { sibling = sibling.previousElementSibling; } if (sibling && !seen.has(sibling)) { current = sibling; console.log(`[${NAME}] Moving to sibling:`, current.tagName); } else { console.log(`[${NAME}] No more siblings, stopping`); break; } } } console.log(`[${NAME}] Extracted ${texts.length} comments, total length: ${texts.join('\n\n').length}`); // Additional pass: Look for any <p> elements with comment markers that might have been missed // This catches the case where the first comment in a thread is a <p> that's a sibling of the DL if (commentDD && commentDD.tagName === 'DD') { const topDL = commentDD.closest('dl'); if (topDL && topDL.previousElementSibling) { let probe = topDL.previousElementSibling; let probeHops = 0; console.log(`[${NAME}] Scanning backwards from top DL for more context...`); while (probe && probeHops++ < 5) { if (probe.tagName === 'P' && !seen.has(probe)) { const probeText = extractCommentText(probe); if (probeText.length > 20 && (probe.querySelector('[data-mw-comment-start]') || probe.querySelector('[data-mw-comment-end]'))) { console.log(`[${NAME}] Found additional preceding comment (${probeText.length} chars):`, probeText.substring(0, 100) + '...'); texts.unshift(probeText); seen.add(probe); } } probe = probe.previousElementSibling; } } } console.log(`[${NAME}] Final extraction: ${texts.length} comments, total length: ${texts.join('\n\n').length}`); if (texts.length > 0) { const fullContext = texts.join('\n\n'); console.log(`[${NAME}] Full context:`, fullContext); return fullContext; } console.warn(`[${NAME}] No context found, returning empty string`); return ''; } catch (err) { console.error(`[${NAME}] Error extracting context:`, err); return ''; } } async function startForWidget(widgetEl) { if (!TOKEN) { showTokenForm(widgetEl); return; } if (widgetState.has(widgetEl) || widgetEl.getAttribute('data-convowizard-processing') === 'true') return; widgetEl.setAttribute('data-convowizard-processing', 'true'); console.log(`[${NAME}] Starting widget processing:`, widgetEl.className); function findInput(scope) { // Prefer DT textbox role, then VE contenteditable surface, then textarea return ( scope.querySelector('div[role="textbox"]') || scope.querySelector('[contenteditable="true"]') || scope.querySelector('.ve-ce-surface [contenteditable="true"]') || scope.querySelector('.ve-ce-surface') || scope.querySelector('textarea') ); } async function waitForInput(scope, timeoutMs = 8000) { const deadline = Date.now() + timeoutMs; return new Promise((resolve) => { const tick = () => { const el = findInput(scope) || findInput(document); if (el) return resolve(el); if (Date.now() > deadline) return resolve(null); setTimeout(tick, 100); }; tick(); }); } const inputEl = await waitForInput(widgetEl); if (!inputEl) { console.warn(`[${NAME}] No input element found in widget (after waiting)`); return; } console.log(`[${NAME}] Input element found:`, inputEl.tagName, inputEl.getAttribute('role') || (inputEl.getAttribute('contenteditable') ? 'contenteditable' : '')); const context = getContextStringFor(widgetEl); console.log(`[${NAME}] Context extracted, length: ${context.length} chars`); console.log(`[${NAME}] Context preview:`, context.substring(0, 200)); // Split context on (UTC) timestamps to create separate utterances like Reddit does // This prevents backend caching bug where all Wikipedia convos share id=None const utterances = context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existing = utterances.map((text, index) => ({ id: `wiki_${Date.now()}_${index}`, // Unique ID prevents cache collision text: text.trim() })); console.log(`[${NAME}] Created ${existing.length} utterances with unique IDs`); existing.forEach((utt, i) => { console.log(`[${NAME}] ${i}: ID=${utt.id}, text=${utt.text.substring(0, 60)}...`); }); const data = await postJson('start', { existing, // Send as array like Reddit, not context string! reply_id: existing.length > 0 ? existing[existing.length - 1].id : null, url: wikiUrlToPostId(location.href), token: TOKEN, username: USERNAME }); console.log(`[${NAME}] Received response from /start:`, data); // Handle invalid token - clear and show auth form if (data && data.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } if (!data || !data.interaction_id) return; widgetState.set(widgetEl, { interactionId: data.interaction_id, inputEl, context }); handleIntervention(widgetEl, inputEl, data); const sendContinue = throttle(async () => { const st = widgetState.get(widgetEl); if (!st) return; // Check if thread is muted const threadId = getThreadIdForWidget(widgetEl); if (threadId && isThreadMuted(threadId)) { // Clear input background if muted if (st.inputEl) { st.inputEl.style.setProperty('background-color', 'transparent', 'important'); } return; } const widgetId = widgetEl.id; const h = document.getElementById(`reply_${widgetId}_h`); if (h) { // Update the SVG icon const existingSvg = h.querySelector('svg:not(.convowizard-mute-btn svg)'); if (existingSvg) { const temp = document.createElement('div'); temp.innerHTML = ` <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"> <path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 13H7v-2h2v2zm0-3H7V4h2v6z"/> </svg> `; const newSvg = temp.querySelector('svg'); if (newSvg) { existingSvg.replaceWith(newSvg); } } // Update text span with processing indicator const textSpan = h.querySelector('span.convowizard-header-text'); if (textSpan) { textSpan.textContent = `${NAME}: Reply Summary (Processing...)`; } } // Recreate existing array from cached context for continue requests const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForContinue = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, // Use interaction_id for consistency text: text.trim() })); const data2 = await postJson('continue', { existing: existingForContinue, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, token: TOKEN, username: USERNAME }); // Handle invalid token on continue if (data2 && data2.error === 'Invalid Token') { TOKEN = null; await persistToken(STORAGE_KEY, null); showTokenForm(widgetEl); return; } handleIntervention(widgetEl, st.inputEl, data2); }, 3000); inputEl.addEventListener('input', sendContinue); inputEl.addEventListener('keyup', sendContinue); inputEl.addEventListener('paste', sendContinue); let postBtn = widgetEl.querySelector(".oo-ui-buttonElement-button[title*='Reply']"); if (!postBtn) { postBtn = Array.from(widgetEl.querySelectorAll('.oo-ui-buttonElement-button')) .find(b => /reply/i.test((b.title || b.innerText || '').trim())); } if (postBtn) { postBtn.addEventListener('click', async () => { const st = widgetState.get(widgetEl); if (!st) return; try { // Recreate existing array from cached context for submit const contextUtterances = st.context.split(/\s*\(UTC\)\s*/).filter(text => text.trim().length > 0); const existingForSubmit = contextUtterances.map((text, index) => ({ id: `wiki_${st.interactionId}_${index}`, text: text.trim() })); await postJson('submit', { existing: existingForSubmit, new: (st.inputEl.innerText || st.inputEl.textContent || st.inputEl.value || ''), interaction_id: st.interactionId, submitted_id: null, token: TOKEN, username: USERNAME }); } finally { widgetState.delete(widgetEl); } }); } } function handleIntervention(widgetEl, inputEl, data) { if (!data) { console.warn(`[${NAME}] handleIntervention called with no data`); return; } console.log(`[${NAME}] handleIntervention called with data:`, data); if (data.error) { return; } const interactionId = data.interaction_id; if (!interactionId) { console.warn(`[${NAME}] No interaction_id in data`); return; } const threadId = getThreadIdForWidget(widgetEl); // Skip display if thread is muted if (threadId && isThreadMuted(threadId)) { const widgetId = widgetEl.id || `widget_${Date.now()}`; if (!widgetEl.id) widgetEl.id = widgetId; ensureUnmuteIndicator(widgetEl, threadId, widgetId); return; } const { replyId, contextId } = ensurePanels(widgetEl, threadId); const showScores = Object.prototype.hasOwnProperty.call(data, 'show_scores'); console.log(`[${NAME}] Panel IDs - context: ${contextId}, reply: ${replyId}`); console.log(`[${NAME}] Show scores: ${showScores}`); const which = (data.which || '').substring(0, 5); console.log(`[${NAME}] Response type (which): ${which}`); if (which === 'craft') { console.log(`[${NAME}] CRAFT mode - craft_ctx_score:`, data.craft_ctx_score); console.log(`[${NAME}] CRAFT mode - craft_reply_score:`, data.craft_reply_score); console.log(`[${NAME}] CRAFT mode - craft_reply_change:`, data.craft_reply_change); if (data.craft_ctx_score != null) { console.log(`[${NAME}] Updating context panel with score: ${data.craft_ctx_score}`); updateContextPanel(contextId, data.craft_ctx_score, showScores); } else { console.log(`[${NAME}] No context score, updating context panel with null`); updateContextPanel(contextId, null, showScores); } if (data.craft_reply_change != null && data.craft_reply_score != null) { console.log(`[${NAME}] Updating reply panel with change: ${data.craft_reply_change}, score: ${data.craft_reply_score}`); updateReplyPanel(replyId, data.craft_reply_change, data.craft_reply_score, showScores, inputEl); } else { console.log(`[${NAME}] No reply scores, updating reply panel with zeros`); updateReplyPanel(replyId, 0, 0, showScores, inputEl); } } else { const noteId = `passivenote${interactionId}_d`; if (!data.message && !document.getElementById(noteId)) { const box = document.createElement('div'); box.id = noteId; box.className = 'passiveModeDisplay'; box.style.cssText = ` margin:8px 0;padding:8px 12px;border-radius:6px;border:1px solid #a2a9b1; background:linear-gradient(135deg,#f8f9fa 0%,#ffffff 100%); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;opacity:.8; `; const content = document.createElement('div'); content.id = `passivenote${interactionId}_p`; content.style.cssText = `font-size:12px;color:#54595d;margin:0;font-style:italic;`; content.textContent = `${NAME} is currently not active on this thread.`; box.appendChild(content); widgetEl.prepend(box); } } } function installClickDelegation() { document.addEventListener('click', (e) => { console.log(`[${NAME}] Click detected on:`, e.target.tagName, e.target.className); // Match reply buttons and links (both old and new DiscussionTools formats) // Need to check the actual clicked element AND traverse up const link = e.target.closest && ( e.target.closest('.ext-discussiontools-init-replybutton') || e.target.closest('.ext-discussiontools-init-replylink-reply') || e.target.closest('a.ext-discussiontools-init-replylink') || e.target.closest('.ext-discussiontools-init-replylink-buttons') || e.target.closest('[class*="ext-discussiontools-init-"][class*="reply"]') ); if (!link) return; console.log(`[${NAME}] Reply link clicked:`, link.className); // For flat discussion structures (like Research talk pages), the widget may be inserted // outside the immediate parent. Search more broadly. const anchorScope = link.closest('dd, li, p, div, section') || document.body; const broadScope = anchorScope.closest('#mw-content-text, .mw-parser-output, #content') || document.body; console.log(`[${NAME}] Anchor scope:`, anchorScope.tagName, anchorScope.className); console.log(`[${NAME}] Broad scope:`, broadScope.tagName, broadScope.className); const timeoutAt = Date.now() + 8000; let attempts = 0; const poll = setInterval(() => { attempts++; // Try multiple search strategies: // 1. In immediate anchor scope (for nested structures like dd/li) // 2. As sibling to anchor scope (for flat structures like p) // 3. In broader content scope // 4. Document-wide fallback const widget = anchorScope.querySelector('.ext-discussiontools-ui-replyWidget') || anchorScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || (anchorScope.nextElementSibling && ( anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-replyWidget') || anchorScope.nextElementSibling.classList.contains('ext-discussiontools-ui-newTopicWidget') ) ? anchorScope.nextElementSibling : null) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-replyWidget:last-child')) || (anchorScope.parentElement && anchorScope.parentElement.querySelector('.ext-discussiontools-ui-newTopicWidget:last-child')) || broadScope.querySelector('.ext-discussiontools-ui-replyWidget') || broadScope.querySelector('.ext-discussiontools-ui-newTopicWidget') || document.querySelector('.ext-discussiontools-ui-replyWidget') || document.querySelector('.ext-discussiontools-ui-newTopicWidget'); if (widget) { console.log(`[${NAME}] Widget found after ${attempts} attempts:`, widget.className); clearInterval(poll); startForWidget(widget); } else if (Date.now() > timeoutAt) { console.log(`[${NAME}] Widget timeout after ${attempts} attempts - no widget found`); console.log(`[${NAME}] Searched in anchorScope:`, anchorScope); console.log(`[${NAME}] Searched in broadScope:`, broadScope); clearInterval(poll); } else if (attempts % 10 === 0) { console.log(`[${NAME}] Still searching for widget, attempt ${attempts}...`); } }, 100); }); } function installWidgetObserver() { const root = document.getElementById('content') || document.body; console.log(`[${NAME}] Setting up mutation observer on:`, root.id || root.tagName); const obs = new MutationObserver((mutations) => { for (const m of mutations) { for (const n of m.addedNodes) { if (!(n instanceof HTMLElement)) continue; // Log all significant additions for debugging if (n.className && typeof n.className === 'string' && n.className.includes('ext-discussiontools')) { console.log(`[${NAME}] DOM addition detected:`, n.tagName, n.className); } // Match specific DiscussionTools widgets if (n.matches('.ext-discussiontools-ui-replyWidget') || n.matches('.ext-discussiontools-ui-newTopicWidget')) { console.log(`[${NAME}] *** Widget detected via observer ***:`, n.className); startForWidget(n); } else { const nested = n.querySelector && ( n.querySelector('.ext-discussiontools-ui-replyWidget') || n.querySelector('.ext-discussiontools-ui-newTopicWidget') ); if (nested) { console.log(`[${NAME}] *** Nested widget detected ***:`, nested.className); startForWidget(nested); } } } } }); obs.observe(root, { childList: true, subtree: true }); console.log(`[${NAME}] Mutation observer is now watching for widget creation`); // Check for existing widgets on page load const existingWidgets = document.querySelectorAll('.ext-discussiontools-ui-replyWidget, .ext-discussiontools-ui-newTopicWidget'); if (existingWidgets.length > 0) { console.log(`[${NAME}] Found ${existingWidgets.length} existing widget(s) on page load`); existingWidgets.forEach(w => { console.log(`[${NAME}] Processing existing widget:`, w.className); startForWidget(w); }); } else { console.log(`[${NAME}] No existing widgets found on page load`); } } // kick off installClickDelegation(); installWidgetObserver(); console.log(`[${NAME}] === INITIALIZATION COMPLETE ===`); console.log(`[${NAME}] Click delegation: ACTIVE`); console.log(`[${NAME}] Mutation observer: ACTIVE`); console.log(`[${NAME}] Waiting for user interaction...`); })(); //</nowiki> s9b6pbtdlk0mtq9jwk4owoxfg7gbcvv User:কমলেশ মন্ডল/common.js 2 171554 739855 724860 2026-04-30T08:44:18Z কমলেশ মন্ডল 72403 739855 javascript text/javascript (function() { if (!['edit', 'submit'].includes(mw.config.get('wgAction'))) return; const cleanupMessyCites = function() { const textbox = document.getElementById('wpTextbox1'); if (!textbox) return; let text = textbox.value; // Regex to find named refs const namedRefRegex = /<ref name\s*=\s*["']([^"']+)["']>([\s\S]*?)<\/ref>/gi; const processedText = text.replace(namedRefRegex, (fullMatch, name, content) => { // 1. Check if it even has a cite tag const citeMatch = /<cite/i.exec(content); if (citeMatch) { // 2. Get everything BEFORE the <cite> tag const preCiteContent = content.substring(0, citeMatch.index); // 3. Check if that "pre-content" contains anything other than spaces/newlines // \S matches any non-whitespace character if (/\S/.test(preCiteContent)) { // There is "info info" before the cite, so collapse it return `<ref name="${name}" />`; } } // Otherwise, leave it exactly as it is return fullMatch; }); if (text === processedText) { mw.notify('No messy named refs found.', { type: 'warn' }); } else { textbox.value = processedText; mw.notify('Collapsed refs with leading junk info.', { type: 'success' }); } }; $(document).ready(function() { const link = mw.util.addPortletLink( 'p-tb', '#', 'Clean Messy Refs', 't-clean-messy', 'Collapses named refs only if there is text before the <cite> tag' ); $(link).click(function(e) { e.preventDefault(); cleanupMessyCites(); }); }); })(); j8dkbe3gnprci5pahjxav4upbixlr8p 739858 739855 2026-04-30T09:03:51Z কমলেশ মন্ডল 72403 739858 javascript text/javascript (function() { if (!['edit', 'submit'].includes(mw.config.get('wgAction'))) return; const cleanupMessyCites = function() { const textbox = document.getElementById('wpTextbox1'); if (!textbox) return; let text = textbox.value; // Regex to find named refs const namedRefRegex = /<ref name\s*=\s*["']([^"']+)["']>([\s\S]*?)<\/ref>/gi; const processedText = text.replace(namedRefRegex, (fullMatch, name, content) => { // 1. Check if it even has a cite tag const citeMatch = /<cite/i.exec(content); if (citeMatch) { // 2. Get everything BEFORE the <cite> tag const preCiteContent = content.substring(0, citeMatch.index); // 3. Check if that "pre-content" contains anything other than spaces/newlines // \S matches any non-whitespace character if (/\S/.test(preCiteContent)) { // There is "info info" before the cite, so collapse it return `<ref name="${name}" />`; } } // Otherwise, leave it exactly as it is return fullMatch; }); if (text === processedText) { mw.notify('No messy named refs found.', { type: 'warn' }); } else { textbox.value = processedText; mw.notify('Collapsed refs with leading junk info.', { type: 'success' }); } }; $(document).ready(function() { const link = mw.util.addPortletLink( 'p-tb', '#', 'Clean Messy Refs', 't-clean-messy', 'Collapses named refs only if there is text before the <cite> tag' ); $(link).click(function(e) { e.preventDefault(); cleanupMessyCites(); const summary = document.getElementById('wpSummary'); if (summary && summary.value === '') { summary.value = "''সূত্র পরিষ্কারক (স্ক্রিপ্ট)''"; } }); }); })(); rnt16cwz9isp6ufzpdhjpvt1juqxu8l 739859 739858 2026-04-30T09:22:03Z কমলেশ মন্ডল 72403 739859 javascript text/javascript (function() { if (!['edit', 'submit'].includes(mw.config.get('wgAction'))) return; const cleanupMessyCites = function() { const textbox = document.getElementById('wpTextbox1'); if (!textbox) return; let text = textbox.value; // Regex to find named refs const namedRefRegex = /<ref name\s*=\s*["']([^"']+)["']>([\s\S]*?)<\/ref>/gi; const processedText = text.replace(namedRefRegex, (fullMatch, name, content) => { // 1. Check if it even has a cite tag const citeMatch = /<cite/i.exec(content); if (citeMatch) { // 2. Get everything BEFORE the <cite> tag const preCiteContent = content.substring(0, citeMatch.index); // 3. Check if that "pre-content" contains anything other than spaces/newlines // \S matches any non-whitespace character if (/\S/.test(preCiteContent)) { // There is "info info" before the cite, so collapse it return `<ref name="${name}" />`; } } // Otherwise, leave it exactly as it is return fullMatch; }); if (text === processedText) { mw.notify('No messy named refs found.', { type: 'warn' }); } else { textbox.value = processedText; const summary = document.getElementById('wpSummary'); if (summary && summary.value === '') { summary.value = "''সূত্র পরিষ্কারক (স্ক্রিপ্ট)''"; } mw.notify('Collapsed refs with leading junk info.', { type: 'success' }); } }; $(document).ready(function() { const link = mw.util.addPortletLink( 'p-tb', '#', 'Clean Messy Refs', 't-clean-messy', 'Collapses named refs only if there is text before the <cite> tag' ); $(link).click(function(e) { e.preventDefault(); setTimeout(cleanupMessyCites, 0); }); }); })(); lbvoeix3u19q0fn55wcxhj2c9ko0eon 739861 739859 2026-04-30T09:35:43Z কমলেশ মন্ডল 72403 739861 javascript text/javascript (function() { if (!['edit', 'submit'].includes(mw.config.get('wgAction'))) return; const cleanupMessyCites = function() { const textbox = document.getElementById('wpTextbox1'); if (!textbox) return; let text = textbox.value; const namedRefRegex = /<ref name\s*=\s*["']([^"']+)["']>([\s\S]*?)<\/ref>/gi; const matches = [...text.matchAll(namedRefRegex)]; if (matches.length === 0) { mw.notify('No messy named refs found.', { type: 'warn' }); return; } let result = text; let changed = 0; const chunkSize = 50; let i = 0; const processChunk = function() { const chunk = matches.slice(i, i + chunkSize); chunk.forEach(match => { const [fullMatch, name, content] = match; const citeMatch = /<cite/i.exec(content); if (citeMatch) { const preCiteContent = content.substring(0, citeMatch.index); if (/\S/.test(preCiteContent)) { result = result.replace(fullMatch, `<ref name="${name}" />`); changed++; } } }); i += chunkSize; if (i < matches.length) { setTimeout(processChunk, 0); } else { if (changed === 0) { mw.notify('No messy named refs found.', { type: 'warn' }); } else { textbox.value = result; const summary = document.getElementById('wpSummary'); if (summary && summary.value === '') { summary.value = "''সূত্র পরিষ্কারক (স্ক্রিপ্ট)''"; } mw.notify(`Collapsed ${changed} refs with leading junk info.`, { type: 'success' }); } } }; setTimeout(processChunk, 0); }; $(document).ready(function() { const link = mw.util.addPortletLink( 'p-tb', '#', 'Clean Messy Refs', 't-clean-messy', 'Collapses named refs only if there is text before the <cite> tag' ); $(link).click(function(e) { e.preventDefault(); setTimeout(cleanupMessyCites, 0); }); }); })(); gyqxqqmkhj90j7rdxyp3juk5ihk7b7j 739864 739861 2026-04-30T10:06:31Z কমলেশ মন্ডল 72403 739864 javascript text/javascript (function() { if (!['edit', 'submit'].includes(mw.config.get('wgAction'))) return; const cleanupMessyCites = function() { const textbox = document.getElementById('wpTextbox1'); if (!textbox) return; let text = textbox.value; const namedRefRegex = /<ref name\s*=\s*["']([^"']+)["']>([\s\S]*?)<\/ref>/gi; const matches = [...text.matchAll(namedRefRegex)]; if (matches.length === 0) { mw.notify('অগোছালো নামযুক্ত তথ্যসূত্র পাওয়া যায়নি।', { type: 'warn' }); return; } let result = text; let changed = 0; const chunkSize = 50; let i = 0; const processChunk = function() { const chunk = matches.slice(i, i + chunkSize); chunk.forEach(match => { const [fullMatch, name, content] = match; const citeMatch = /<cite/i.exec(content); if (citeMatch) { const preCiteContent = content.substring(0, citeMatch.index); if (/\S/.test(preCiteContent)) { result = result.replace(fullMatch, `<ref name="${name}" />`); changed++; } } }); i += chunkSize; if (i < matches.length) { setTimeout(processChunk, 0); } else { if (changed === 0) { mw.notify('অগোছালো নামযুক্ত তথ্যসূত্র পাওয়া যায়নি।', { type: 'warn' }); } else { textbox.value = result; const summary = document.getElementById('wpSummary'); if (summary && summary.value === '') { summary.value = "''সূত্র পরিষ্কারক (স্ক্রিপ্ট)''"; } mw.notify(`${changed}টি অগোছালো তথ্যসূত্র পরিষ্কার করা হয়েছে।`, { type: 'success' }); } } }; setTimeout(processChunk, 0); }; $(document).ready(function() { const link = mw.util.addPortletLink( 'p-tb', '#', 'অগোছালো সূত্র পরিষ্কারক', 't-clean-messy', 'Collapses named refs only if there is text before the <cite> tag' ); $(link).click(function(e) { e.preventDefault(); setTimeout(cleanupMessyCites, 0); }); }); })(); a6wx1u6q4yedthkhxs7gwzgr9yvgogi 739866 739864 2026-04-30T10:10:13Z কমলেশ মন্ডল 72403 739866 javascript text/javascript (function() { if (!['edit', 'submit'].includes(mw.config.get('wgAction'))) return; const toBengaliNumber = n => n.toString().replace(/\d/g, d => '০১২৩৪৫৬৭৮৯'[d]); const cleanupMessyCites = function() { const textbox = document.getElementById('wpTextbox1'); if (!textbox) return; let text = textbox.value; const namedRefRegex = /<ref name\s*=\s*["']([^"']+)["']>([\s\S]*?)<\/ref>/gi; const matches = [...text.matchAll(namedRefRegex)]; if (matches.length === 0) { mw.notify('অগোছালো নামযুক্ত তথ্যসূত্র পাওয়া যায়নি।', { type: 'warn' }); return; } let result = text; let changed = 0; const chunkSize = 50; let i = 0; const processChunk = function() { const chunk = matches.slice(i, i + chunkSize); chunk.forEach(match => { const [fullMatch, name, content] = match; const citeMatch = /<cite/i.exec(content); if (citeMatch) { const preCiteContent = content.substring(0, citeMatch.index); if (/\S/.test(preCiteContent)) { result = result.replace(fullMatch, `<ref name="${name}" />`); changed++; } } }); i += chunkSize; if (i < matches.length) { setTimeout(processChunk, 0); } else { if (changed === 0) { mw.notify('অগোছালো নামযুক্ত তথ্যসূত্র পাওয়া যায়নি।', { type: 'warn' }); } else { textbox.value = result; const summary = document.getElementById('wpSummary'); if (summary && summary.value === '') { summary.value = "''সূত্র পরিষ্কারক (স্ক্রিপ্ট)''"; } mw.notify(`${toBengaliNumber(changed)}টি অগোছালো তথ্যসূত্র পরিষ্কার করা হয়েছে।`, { type: 'success' }); } } }; setTimeout(processChunk, 0); }; $(document).ready(function() { const link = mw.util.addPortletLink( 'p-tb', '#', 'অগোছালো সূত্র পরিষ্কারক', 't-clean-messy', 'Collapses named refs only if there is text before the <cite> tag' ); $(link).click(function(e) { e.preventDefault(); setTimeout(cleanupMessyCites, 0); }); }); })(); bebkuuprxctzxkxluunydkc9g7t4gh4 739874 739866 2026-04-30T11:27:16Z কমলেশ মন্ডল 72403 739874 javascript text/javascript (function() { if (!['edit', 'submit'].includes(mw.config.get('wgAction'))) return; const toBengaliNumber = n => n.toString().replace(/\d/g, d => '০১২৩৪৫৬৭৮৯'[d]); const cleanupMessyCites = function() { const textbox = document.getElementById('wpTextbox1'); if (!textbox) return; let text = textbox.value; const namedRefRegex = /<ref name\s*=\s*["']([^"']+)["']>([\s\S]*?)<\/ref>/gi; const matches = [...text.matchAll(namedRefRegex)]; if (matches.length === 0) { mw.notify('অগোছালো নামযুক্ত তথ্যসূত্র পাওয়া যায়নি।', { type: 'warn' }); return; } let result = text; let changed = 0; const chunkSize = 50; let i = 0; const processChunk = function() { const chunk = matches.slice(i, i + chunkSize); chunk.forEach(match => { const [fullMatch, name, content] = match; const citeMatch = /<cite/i.exec(content); if (citeMatch) { const preCiteContent = content.substring(0, citeMatch.index); if (/\S/.test(preCiteContent)) { result = result.replace(fullMatch, `<ref name="${name}" />`); changed++; } } }); i += chunkSize; if (i < matches.length) { setTimeout(processChunk, 0); } else { if (changed === 0) { mw.notify('অগোছালো নামযুক্ত তথ্যসূত্র পাওয়া যায়নি।', { type: 'warn' }); } else { textbox.value = result; const summary = document.getElementById('wpSummary'); if (summary && summary.value === '') { summary.value = "''সূত্র পরিষ্কারক (স্ক্রিপ্ট)''"; } const minor = document.getElementById('wpMinoredit'); if (minor) minor.checked = true; mw.notify(`${toBengaliNumber(changed)}টি অগোছালো তথ্যসূত্র পরিষ্কার করা হয়েছে।`, { type: 'success' }); } } }; setTimeout(processChunk, 0); }; $(document).ready(function() { const link = mw.util.addPortletLink( 'p-tb', '#', 'অগোছালো সূত্র পরিষ্কারক', 't-clean-messy', 'Collapses named refs only if there is text before the <cite> tag' ); $(link).click(function(e) { e.preventDefault(); setTimeout(cleanupMessyCites, 0); }); }); })(); qlolupiswxd6kb7qy5d6anligcrmubj 739879 739874 2026-04-30T11:34:56Z কমলেশ মন্ডল 72403 739879 javascript text/javascript (function() { if (!['edit', 'submit'].includes(mw.config.get('wgAction'))) return; const toBengaliNumber = n => n.toString().replace(/\d/g, d => '০১২৩৪৫৬৭৮৯'[d]); const cleanupMessyCites = function() { const textbox = document.getElementById('wpTextbox1'); if (!textbox) return; let text = textbox.value; const namedRefRegex = /<ref name\s*=\s*["']([^"']+)["']>([\s\S]*?)<\/ref>/gi; const matches = [...text.matchAll(namedRefRegex)]; if (matches.length === 0) { mw.notify('অগোছালো নামযুক্ত তথ্যসূত্র পাওয়া যায়নি।', { type: 'warn' }); return; } let result = text; let changed = 0; const chunkSize = 50; let i = 0; const processChunk = function() { const chunk = matches.slice(i, i + chunkSize); chunk.forEach(match => { const [fullMatch, name, content] = match; const citeMatch = /<cite/i.exec(content); if (citeMatch) { const preCiteContent = content.substring(0, citeMatch.index); if (/\S/.test(preCiteContent)) { result = result.replace(fullMatch, `<ref name="${name}" />`); changed++; } } }); i += chunkSize; if (i < matches.length) { setTimeout(processChunk, 0); } else { if (changed === 0) { mw.notify('অগোছালো নামযুক্ত তথ্যসূত্র পাওয়া যায়নি।', { type: 'warn' }); } else { textbox.value = result; const summary = document.getElementById('wpSummary'); if (summary && summary.value === '') { summary.value = 'সূত্র পরিষ্কারক (স্ক্রিপ্ট)'; } const minor = document.getElementById('wpMinoredit'); if (minor) minor.checked = true; mw.notify(`${toBengaliNumber(changed)}টি অগোছালো তথ্যসূত্র পরিষ্কার করা হয়েছে।`, { type: 'success' }); } } }; setTimeout(processChunk, 0); }; $(document).ready(function() { const link = mw.util.addPortletLink( 'p-tb', '#', 'অগোছালো সূত্র পরিষ্কারক', 't-clean-messy', 'Collapses named refs only if there is text before the <cite> tag' ); $(link).click(function(e) { e.preventDefault(); setTimeout(cleanupMessyCites, 0); }); }); })(); 3yhcyu2l1vul6na1uf8f21fv1qa6dp8 User:Enbi/common.js 2 172174 739794 738671 2026-04-29T20:16:06Z Enbi 72574 Installing [[User:Enbi/test2.js]] ([[User:Enterprisey/script-installer|script-installer]]) 739794 javascript text/javascript importScript('User:Enbi/UnnamedScript.js'); importScript('User:Enbi/testScript.js'); importScript('User:Enbi/simul.js'); importScript('User:Enbi/newUserFilter.js'); importScript('User:Enbi/new.js'); importScript('User:Enbi/wikiCMD.js'); importScript('User:enbi/LTA-undo.js'); importScript('User:Enbi/LACsuppresssed.js'); importScript('w:en:MediaWiki:Gadget-script-installer.js') importScript('User:Enbi/highlightUsernamePatterns.js'); // Backlink: [[User:Enbi/highlightUsernamePatterns.js]] importScript('User:Enbi/testScript2.js'); // Backlink: [[User:Enbi/testScript2.js]] importScript('User:Enbi/editAlert.js'); // Backlink: [[User:Enbi/editAlert.js]] importScript('User:Enbi/searchReplace.js'); // Backlink: [[User:Enbi/searchReplace.js]] importScript('User:Enbi/LACSRG.js'); // Backlink: [[User:Enbi/LACSRG.js]] importScript('User:Enbi/test2.js'); // Backlink: [[User:Enbi/test2.js]] 9ycrbairuuftph622d4osuc39n563sa MediaWiki:AutoModeratorMultilingualConfig.json 8 174236 739787 737218 2026-04-29T18:29:05Z KGraessle-WMF 61195 739787 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": "", "AutoModeratorMultilingualEnableLogOnlyMode": true, "$version": "1.0.0" } j6xd7dyeasgzj3t15zhhth52xhmn2ws 739790 739787 2026-04-29T18:36:02Z KGraessle-WMF 61195 739790 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": "", "AutoModeratorMultilingualEnableLogOnlyMode": false, "$version": "1.0.0" } 7aoji5k8sq56ttn2udw5bxxan06vlid User:MrJaroslavik/MarkAdmins.js 2 174970 739786 739388 2026-04-29T17:39:44Z MrJaroslavik 44012 + 739786 javascript text/javascript // MarkAdmins.js // Based on: https://meta.wikimedia.org/wiki/MediaWiki:Gadget-markAdmins.js // ------------------------------------------------------- // Features: // - Automated API fetching (no static JSON required) // - Support for Local (allusers) and Global (CentralAuth) groups // - on others wiki than metawiki shows some local metawiki groups with global effect - wmf-officeit, wmf-supportsafety, global-renamer // - Displays important global roles from Meta-Wiki on all projects. // - 24h cache of saved users in groups - use this in console F12 to reset: // - localStorage.removeItem('markAdmins_v2026_cache'); location.reload(); // - API pagination support (handles >500 users, e.g., on Enwiki) // - Localized namespace detection // - Skips talk pages and subpages // - Priority list of groups // - With help of Gemini 3 // ------------------------------------------------------- // <nowiki> (function(mw, $) { 'use strict'; const markAdmins = mw.libs.markAdmins = { config: { // GROUP CONFIGURATION - IF YOU ADD LOCAL OR GLOBAL GROUP, DON´T FORGOT TO ALSO ADD IT BELOW - const lCandidate and const gCandidates // Define labels and set "enabled: true" to display the tag or "enabled: false" to hide groups: { // LOCAL GROUPS (Fetched via list=allusers) 'sysop': { label: 'A', enabled: true }, 'bureaucrat': { label: 'B', enabled: true }, 'checkuser': { label: 'CU', enabled: true }, 'suppress': { label: 'OS', enabled: true }, 'interface-admin': { label: 'IA', enabled: true }, 'centralnoticeadmin': { label: 'CNA', enabled: true }, 'patroller': { label: 'Pat', enabled: true }, 'rollbacker': { label: 'Rol', enabled: true }, 'translationadmin': { label: 'TA', enabled: true }, 'wmf-officeit': { label: 'WMFOIT', enabled: true }, 'wmf-supportsafety': { label: 'WMF T&S', enabled: true }, 'global-renamer': { label: 'GRN', enabled: true }, 'bot': { label: 'Bot', enabled: true }, 'accountcreator': { label: 'ACC', enabled: true }, 'arbcom': { label: 'ArbCom', enabled: true }, // GLOBAL GROUPS (Fetched via list=globalallusers / CentralAuth) 'steward': { label: 'S', enabled: true }, 'staff': { label: 'Staff', enabled: true }, 'global-sysop': { label: 'GS', enabled: true }, 'ombuds': { label: 'Omb', enabled: true }, 'u4c-member': { label: 'U4C', enabled: true }, 'sysadmin': { label: 'sysadmin', enabled: true }, 'abusefilter-helper': { label: 'AFH', enabled: true }, 'abusefilter-maintainer': { label: 'AFM', enabled: true }, 'global-rollbacker': { label: 'GR', enabled: true }, 'vrt-permissions': { label: 'VRT', enabled: true }, 'apihighlimits-requestor': { label: 'API', enabled: true }, 'global-bot': { label: 'GBOT', enabled: true }, 'global-interface-editor': { label: 'GIE', enabled: true }, 'new-wikis-importer': { label: 'NWI', enabled: true }, } }, users: {}, // Fetch privileged users from API with pagination support fetchData: async function() { const cacheKey = 'markAdmins_v2026_cache'; // Updated version key const cached = localStorage.getItem(cacheKey); const cacheTime = localStorage.getItem(`${cacheKey}_time`); // Cache check: Ensure cacheTime is treated as a number if (cached && cacheTime && (Date.now() - Number(cacheTime) < 86400000)) { return JSON.parse(cached); } const api = new mw.Api(); const userMap = {}; // Updated helper function with optional filtering to prevent group leakage async function fetchAndMerge(apiInstance, params, listName, allowedGroups) { try { let res = await apiInstance.get(params); const processUsers = (users) => { users?.forEach(({ name, groups }) => { if (!userMap[name]) userMap[name] = []; const groupsToAdd = allowedGroups ? groups.filter(g => allowedGroups.includes(g)) : groups; userMap[name] = [...new Set([...userMap[name], ...groupsToAdd])]; }); }; processUsers(res.query?.[listName]); while (res.continue) { res = await apiInstance.get({ ...params, ...res.continue }); processUsers(res.query?.[listName]); } } catch (e) { console.error("MarkAdmins API error", e); } } // List of candidates const lCandidates = ['sysop', 'bureaucrat', 'checkuser', 'suppress', 'interface-admin', 'centralnoticeadmin', 'patroller', 'rollbacker', 'translationadmin', 'wmf-officeit', 'wmf-supportsafety', 'global-renamer', 'bot']; const gCandidates = ['steward', 'staff', 'global-sysop', 'ombuds', 'u4c-member', 'sysadmin', 'abusefilter-helper', 'abusefilter-maintainer', 'global-interface-editor', 'global-rollbacker', 'vrt-permissions']; // 1. Fetch Local groups await fetchAndMerge(api, { action: 'query', list: 'allusers', augroup: lCandidates.join('|'), auprop: 'groups', aulimit: 'max', formatversion: 2 }, 'allusers'); // 2. Fetch Global groups await fetchAndMerge(api, { action: 'query', list: 'globalallusers', agugroup: gCandidates.join('|'), aguprop: 'groups', agulimit: 'max', formatversion: 2 }, 'globalallusers'); // 3. Remote call to Meta-Wiki: strictly filtered to avoid showing TA/CNA on other wikis if (mw.config.get('wgDBname') !== 'metawiki') { const metaApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php', { anonymous: true }); const mCandidates = ['wmf-officeit', 'wmf-supportsafety', 'global-renamer']; await fetchAndMerge(metaApi, { action: 'query', list: 'allusers', augroup: mCandidates.join('|'), auprop: 'groups', aulimit: 'max', formatversion: 2 }, 'allusers', mCandidates); } localStorage.setItem(cacheKey, JSON.stringify(userMap)); localStorage.setItem(cacheKey + '_time', Date.now()); return userMap; }, /** * Initialize gadget, detect local namespaces and setup hooks */ init: async function() { const self = this; await mw.loader.using(['mediawiki.api', 'mediawiki.ForeignApi', 'user', 'mediawiki.util']); const nsIds = mw.config.get('wgNamespaceIds'); const userNamespaces = Object.keys(nsIds).filter(name => nsIds[name] === 2); const nsPattern = userNamespaces.join('|').replace(/ /g, '_'); self.userRegex = new RegExp('(?:/wiki/|title=)(?:' + nsPattern + '):([^/\\s&?#]+)(?=[#?]|\\s|$)', 'i'); self.users = await self.fetchData(); self.currentDomain = mw.config.get('wgServerName'); mw.hook('wikipage.content').add(function($content) { self.processLinks($content); }); }, // Scan links in the provided content and append labels to matched users processLinks: function($content) { const self = this; const priorityOrder = ['sysop', 'steward', 'bureaucrat', 'checkuser', 'suppress', 'interface-admin', 'translationadmin', 'patroller', 'rollbacker', 'staff', 'sysadmin', 'vrt-permissions']; const priorityMap = Object.fromEntries(priorityOrder.map((id, index) => [id, index])); $content.find('a[href]').each(function() { const $a = $(this); const href = $a.attr('href'); if (!href || $a.hasClass('markadmins-done') || $a.hasClass('extiw') || $a.hasClass('external')) return; // Only process internal links or links matching the current domain (ignores Interwiki links in CentralAuth tables) const isRelative = href.startsWith('/') && !href.startsWith('//'); const isSameDomain = href.includes('//' + self.currentDomain); if (!isRelative && !isSameDomain) return; const match = href.match(self.userRegex); if (match) { let user = decodeURIComponent(match[1].split('#')[0]).replace(/_/g, ' '); // Standardize name: First letter uppercase to match API data if (user) user = user.charAt(0).toUpperCase() + user.slice(1); if (self.users[user]) { const labels = [...self.users[user]].sort((a, b) => { const pA = priorityMap[a] ?? 999; const pB = priorityMap[b] ?? 999; return pA !== pB ? pA - pB : a.localeCompare(b); }).map(g => { const gConfig = self.config.groups[g]; return (gConfig && gConfig.enabled) ? gConfig.label : null; }).filter((label, index, arr) => label && arr.indexOf(label) === index); if (labels.length > 0) { $a.append(`<b class="adminMark">&nbsp;(${labels.join('/')})</b>`); } } $a.addClass('markadmins-done'); } }); } }; markAdmins.init(); }(mediaWiki, jQuery)); // </nowiki> 25qrrduo470lgsxfap5iwk4oqpxc4kf User:Sam Sailor/test.js/comrade-lib.js 2 175022 739824 739699 2026-04-30T05:53:27Z Sam Sailor 26820 Test 739824 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = function() { // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] const text = $('.article_quality').text(); if (!text) return null; if (text.includes('Start')) return 'Start'; if (text.includes('C')) return 'C'; if (text.includes('B')) return 'B'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const wikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}]*stub\}\}/i.test(wikitext); const $raterLink = $('#ca-rater, #t-rater'); const isRaterAvailable = (typeof window.rater !== 'undefined' || $raterLink.length > 0); let talkClass = null; if (!talkPage.missing) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); talkClass = classMatch ? classMatch[1].charAt(0).toUpperCase() + classMatch[1].slice(1).toLowerCase() : null; } const isStubRated = talkClass === 'Stub'; const lingeringStubTag = (talkClass && talkClass !== 'Stub' && oresClass !== 'Stub' && hasStubTag); if (talkPage.missing || isStubRated || lingeringStubTag) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; let $btn; if (isRaterAvailable) { $btn = $('<button>').text('Open Rater').on('click', () => $raterLink.find('a')[0].click()); } else { $btn = $('<button>').text('Install Rater').on('click', () => window.open('https://en.wikipedia.org/wiki/User:Evad37/rater', '_blank')); message += "Consider installing <i>Rater</i> for easy assessment."; } $content.append(message, $btn); } else if (lingeringStubTag && !isStubRated) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btn = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btn); } else if (isStubRated) { message = `Talk page says Stub, but ORES predicts <b>${oresClass}</b>. `; const $btnOres = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnOres); if (oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0\u00A0\u00A0or\u00A0", $btnStart); } else if (oresClass === 'B') { const $btnC = $('<button>').text(`Promote to C`).on('click', () => ComradeLib.performPromotion('C')); $content.append("\u00A0\u00A0\u00A0or\u00A0", $btnC); } } $header.append($content); $container.append($header); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; const newWikitext = wikitext.replace(/\{\{[^}]*stub\}\}\n?/gi, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; // Fallback in case a different class is passed const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; talkWikitext = talkWikitext.replace(/(\|class\s*=\s*)Stub/i, `$1${newClass}`); await ComradeLib.api.postWithEditToken({ action: 'edit', title: talkName, text: talkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}]*?-stub\}\}\s*\n?/gi; const genericStubRegex = /\{\{stub\}\}\s*/gi; artWikitext = artWikitext.replace(stubRegex, '').replace(genericStubRegex, ''); await ComradeLib.api.postWithEditToken({ action: 'edit', title: pageName, text: artWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}` }); location.reload(); } catch (e) { alert("Promotion failed. Check console for details."); console.error(e); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { ligature: { 'æ': 'ae', 'Æ': 'AE', 'œ': 'oe', 'Œ': 'OE', 'ij': 'ij', 'IJ': 'IJ', 'ß': 'ss', 'ẞ': 'SS' }, asciiOnly: { 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'TH', 'ŋ': 'ng', 'Ŋ': 'NG' } }; const hasLigature = /[æÆœŒijIJßẞ]/.test(currentTitle); const hasAsciiOnly = /[ðÐøØłŁþÞŋŊ]/.test(currentTitle); // Ligatures let ligatureTitle = currentTitle.replace(/[æÆœŒijIJßẞ]/g, m => maps.ligature[m]); // ASCII-only (non-diacritic symbols) let asciiOnlyTitle = currentTitle.replace(/[ðÐøØłŁþÞŋŊ]/g, m => maps.asciiOnly[m]); // Standard diacritics let diacriticTitle = currentTitle.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ligature: hasLigature && ligatureTitle !== currentTitle ? ligatureTitle : null, asciiOnly: hasAsciiOnly && asciiOnlyTitle !== currentTitle ? asciiOnlyTitle : null, diacritic: diacriticTitle !== currentTitle ? diacriticTitle : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> htdxzsx4uvf8t0pxewh4a5jyqcti7ia 739826 739824 2026-04-30T05:59:39Z Sam Sailor 26820 Test 739826 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = function() { // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] const text = $('.article_quality').text(); if (!text) return null; if (text.includes('Start')) return 'Start'; if (text.includes('C')) return 'C'; if (text.includes('B')) return 'B'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const wikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}]*stub\}\}/i.test(wikitext); const $raterLink = $('#ca-rater, #t-rater'); const isRaterAvailable = (typeof window.rater !== 'undefined' || $raterLink.length > 0); let talkClass = null; if (!talkPage.missing) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); talkClass = classMatch ? classMatch[1].charAt(0).toUpperCase() + classMatch[1].slice(1).toLowerCase() : null; } const isStubRated = talkClass === 'Stub'; const lingeringStubTag = (talkClass && talkClass !== 'Stub' && oresClass !== 'Stub' && hasStubTag); if (talkPage.missing || isStubRated || lingeringStubTag) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; let $btn; if (isRaterAvailable) { $btn = $('<button>').text('Open Rater').on('click', () => $raterLink.find('a')[0].click()); } else { $btn = $('<button>').text('Install Rater').on('click', () => window.open('https://en.wikipedia.org/wiki/User:Evad37/rater', '_blank')); message += "Consider installing <i>Rater</i> for easy assessment."; } $content.append(message, $btn); } else if (lingeringStubTag && !isStubRated) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btn = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btn); } else if (isStubRated) { message = `Talk page says Stub, but ORES predicts <b>${oresClass}</b>. `; const $btnOres = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnOres); if (oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0\u00A0\u00A0or\u00A0", $btnStart); } else if (oresClass === 'B') { const $btnC = $('<button>').text(`Promote to C`).on('click', () => ComradeLib.performPromotion('C')); $content.append("\u00A0\u00A0\u00A0or\u00A0", $btnC); } } $header.append($content); $container.append($header); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; const newWikitext = wikitext.replace(/\{\{[^}]*stub\}\}\n?/gi, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; // Fallback in case a different class is passed const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; talkWikitext = talkWikitext.replace(/(\|class\s*=\s*)Stub/i, `$1${newClass}`); await ComradeLib.api.postWithEditToken({ action: 'edit', title: talkName, text: talkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}]*?-stub\}\}\s*\n?/gi; const genericStubRegex = /\{\{stub\}\}\s*/gi; artWikitext = artWikitext.replace(stubRegex, '').replace(genericStubRegex, ''); await ComradeLib.api.postWithEditToken({ action: 'edit', title: pageName, text: artWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}` }); location.reload(); } catch (e) { alert("Promotion failed. Check console for details."); console.error(e); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { ligature: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS' }, asciiOnly: { 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; const smartReplace = (str, map) => { return str.split('').map((char, index) => { if (!map[char]) return char; let replacement = map[char]; if (index > 0 && replacement.length > 1) { replacement = replacement.toLowerCase(); } return replacement; }).join(''); }; const ligatureTitle = smartReplace(currentTitle, maps.ligature); const asciiOnlyTitle = smartReplace(currentTitle, maps.asciiOnly); const diacriticTitle = currentTitle.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ligature: (/[æÆœŒijIJßẞ]/.test(currentTitle) && ligatureTitle !== currentTitle) ? ligatureTitle : null, asciiOnly: (/[ðÐøØłŁþÞŋŊ]/.test(currentTitle) && asciiOnlyTitle !== currentTitle) ? asciiOnlyTitle : null, diacritic: (diacriticTitle !== currentTitle) ? diacriticTitle : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 3gl6xl83yfmlkh36vrfc315170x2inc 739828 739826 2026-04-30T06:11:38Z Sam Sailor 26820 Test 739828 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction window.ComradeLib.getOresPrediction = function() { // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] const text = $('.article_quality').text(); if (!text) return null; if (text.includes('Start')) return 'Start'; if (text.includes('C')) return 'C'; if (text.includes('B')) return 'B'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const wikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}]*stub\}\}/i.test(wikitext); const $raterLink = $('#ca-rater, #t-rater'); const isRaterAvailable = (typeof window.rater !== 'undefined' || $raterLink.length > 0); let talkClass = null; if (!talkPage.missing) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); talkClass = classMatch ? classMatch[1].charAt(0).toUpperCase() + classMatch[1].slice(1).toLowerCase() : null; } const isStubRated = talkClass === 'Stub'; const lingeringStubTag = (talkClass && talkClass !== 'Stub' && oresClass !== 'Stub' && hasStubTag); if (talkPage.missing || isStubRated || lingeringStubTag) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; let $btn; if (isRaterAvailable) { $btn = $('<button>').text('Open Rater').on('click', () => $raterLink.find('a')[0].click()); } else { $btn = $('<button>').text('Install Rater').on('click', () => window.open('https://en.wikipedia.org/wiki/User:Evad37/rater', '_blank')); message += "Consider installing <i>Rater</i> for easy assessment."; } $content.append(message, $btn); } else if (lingeringStubTag && !isStubRated) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btn = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btn); } else if (isStubRated) { message = `Talk page says Stub, but ORES predicts <b>${oresClass}</b>. `; const $btnOres = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnOres); if (oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0\u00A0\u00A0or\u00A0", $btnStart); } else if (oresClass === 'B') { const $btnC = $('<button>').text(`Promote to C`).on('click', () => ComradeLib.performPromotion('C')); $content.append("\u00A0\u00A0\u00A0or\u00A0", $btnC); } } $header.append($content); $container.append($header); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; const newWikitext = wikitext.replace(/\{\{[^}]*stub\}\}\n?/gi, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; // Fallback in case a different class is passed const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; talkWikitext = talkWikitext.replace(/(\|class\s*=\s*)Stub/i, `$1${newClass}`); await ComradeLib.api.postWithEditToken({ action: 'edit', title: talkName, text: talkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}]*?-stub\}\}\s*\n?/gi; const genericStubRegex = /\{\{stub\}\}\s*/gi; artWikitext = artWikitext.replace(stubRegex, '').replace(genericStubRegex, ''); await ComradeLib.api.postWithEditToken({ action: 'edit', title: pageName, text: artWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}` }); location.reload(); } catch (e) { alert("Promotion failed. Check console for details."); console.error(e); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> ezmaum56xizq1mrt0vfr7j5h9iwjprq 739833 739828 2026-04-30T06:41:38Z Sam Sailor 26820 Test 739833 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] window.ComradeLib.getOresPrediction = function() { const $el = $('.article_quality'); if (!$el.length) return null; const text = $el.text(); if (/Start/i.test(text)) return 'Start'; if (/^C$/i.test(text) || /\bC\b/.test(text)) return 'C'; if (/^B$/i.test(text) || /\bB\b/.test(text)) return 'B'; if (/Stub/i.test(text)) return 'Stub'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const wikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}]*stub\}\}/i.test(wikitext); const $raterLink = $('#ca-rater, #t-rater'); const isRaterAvailable = (typeof window.rater !== 'undefined' || $raterLink.length > 0); let talkClass = null; if (!talkPage.missing) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); talkClass = classMatch ? classMatch[1].charAt(0).toUpperCase() + classMatch[1].slice(1).toLowerCase() : null; } const isStubRated = talkClass === 'Stub'; const lingeringStubTag = (talkClass && talkClass !== 'Stub' && oresClass !== 'Stub' && hasStubTag); if (talkPage.missing || isStubRated || lingeringStubTag) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; let $btn; if (isRaterAvailable) { $btn = $('<button>').text('Open Rater').on('click', () => $raterLink.find('a')[0].click()); } else { $btn = $('<button>').text('Install Rater').on('click', () => window.open('https://en.wikipedia.org/wiki/User:Evad37/rater', '_blank')); message += "Consider installing <i>Rater</i> for easy assessment."; } $content.append(message, $btn); } else if (lingeringStubTag && !isStubRated) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btn = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btn); } else if (isStubRated) { message = `Talk page says Stub, but ORES predicts <b>${oresClass}</b>. `; const $btnOres = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnOres); if (oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0\u00A0\u00A0or\u00A0", $btnStart); } else if (oresClass === 'B') { const $btnC = $('<button>').text(`Promote to C`).on('click', () => ComradeLib.performPromotion('C')); $content.append("\u00A0\u00A0\u00A0or\u00A0", $btnC); } } $header.append($content); $container.append($header); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; const newWikitext = wikitext.replace(/\{\{[^}]*stub\}\}\n?/gi, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; // Fallback in case a different class is passed const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; talkWikitext = talkWikitext.replace(/(\|class\s*=\s*)Stub/i, `$1${newClass}`); await ComradeLib.api.postWithEditToken({ action: 'edit', title: talkName, text: talkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}]*?-stub\}\}\s*\n?/gi; const genericStubRegex = /\{\{stub\}\}\s*/gi; artWikitext = artWikitext.replace(stubRegex, '').replace(genericStubRegex, ''); await ComradeLib.api.postWithEditToken({ action: 'edit', title: pageName, text: artWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}` }); location.reload(); } catch (e) { alert("Promotion failed. Check console for details."); console.error(e); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> b9r3htsaols17zh7h9uoe9n0otzlw3r 739836 739833 2026-04-30T07:07:03Z Sam Sailor 26820 Test 739836 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] window.ComradeLib.getOresPrediction = function() { const $el = $('.article_quality'); if (!$el.length) return null; const text = $el.text().trim(); if (/\bB\b/.test(text)) return 'B'; if (/\bC\b/.test(text)) return 'C'; if (/\bStart\b/i.test(text)) return 'Start'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const wikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}]*stub\}\}/i.test(wikitext); const $raterLink = $('#ca-rater, #t-rater'); const isRaterAvailable = (typeof window.rater !== 'undefined' || $raterLink.length > 0); let talkClass = null; if (!talkPage.missing) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); talkClass = classMatch ? classMatch[1].charAt(0).toUpperCase() + classMatch[1].slice(1).toLowerCase() : null; } const isStubRated = talkClass === 'Stub'; const lingeringStubTag = (talkClass && talkClass !== 'Stub' && oresClass !== 'Stub' && hasStubTag); if (talkPage.missing || isStubRated || lingeringStubTag) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; let $btn; if (isRaterAvailable) { $btn = $('<button>').text('Open Rater').on('click', () => $raterLink.find('a')[0].click()); } else { $btn = $('<button>').text('Install Rater').on('click', () => window.open('https://en.wikipedia.org/wiki/User:Evad37/rater', '_blank')); message += "Consider installing <i>Rater</i> for easy assessment."; } $content.append(message, $btn); } else if (lingeringStubTag && !isStubRated) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btn = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btn); } else if (isStubRated) { message = `Talk page says Stub, but ORES predicts <b>${oresClass}</b>. `; const $btnOres = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnOres); if (oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0\u00A0\u00A0or\u00A0", $btnStart); } else if (oresClass === 'B') { const $btnC = $('<button>').text(`Promote to C`).on('click', () => ComradeLib.performPromotion('C')); $content.append("\u00A0\u00A0\u00A0or\u00A0", $btnC); } } $header.append($content); $container.append($header); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; const newWikitext = wikitext.replace(/\{\{[^}]*stub\}\}\n?/gi, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; // Fallback in case a different class is passed const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; talkWikitext = talkWikitext.replace(/(\|class\s*=\s*)Stub/i, `$1${newClass}`); await ComradeLib.api.postWithEditToken({ action: 'edit', title: talkName, text: talkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}]*?-stub\}\}\s*\n?/gi; const genericStubRegex = /\{\{stub\}\}\s*/gi; artWikitext = artWikitext.replace(stubRegex, '').replace(genericStubRegex, ''); await ComradeLib.api.postWithEditToken({ action: 'edit', title: pageName, text: artWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}` }); location.reload(); } catch (e) { alert("Promotion failed. Check console for details."); console.error(e); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> bd291n4n01aq8g75ow0dz1xjy96weiq 739837 739836 2026-04-30T07:25:24Z Sam Sailor 26820 Test 739837 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] window.ComradeLib.getOresPrediction = function() { const $el = $('.article_quality'); if (!$el.length) return null; const text = $el.text().trim(); if (/\bFA\b/.test(text)) return 'Fa'; if (/\bGA\b/.test(text)) return 'Ga'; if (/\bB\b/.test(text)) return 'B'; if (/\bC\b/.test(text)) return 'C'; if (/\bStart\b/i.test(text)) return 'Start'; if (/Stub/i.test(text)) return 'Stub'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const wikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}]*stub\}\}/i.test(wikitext); let talkClass = null; if (!talkPage.missing) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); talkClass = classMatch ? classMatch[1].charAt(0).toUpperCase() + classMatch[1].slice(1).toLowerCase() : null; } const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const highQuality = ['B', 'Ga', 'Fa'].includes(oresClass); if (talkPage.missing || isStubRated || (isStartRated && oresClass === 'C') || highQuality) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (highQuality && talkClass !== oresClass) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnRead = $('<button>').text('Read').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); }); $content.append(message, $btnRead); } else if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (isStartRated && oresClass === 'C') { const $btnC = $('<button>').text(`Promote to C`).on('click', () => ComradeLib.performPromotion('C')); $content.append(message, $btnC); } else if (isStubRated) { const $btnOres = $('<button>').text(`Promote to ${oresClass === 'C' ? 'C' : 'Start'}`).on('click', () => ComradeLib.performPromotion(oresClass === 'C' ? 'C' : 'Start')); $content.append(message, $btnOres); if (oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; const newWikitext = wikitext.replace(/\{\{[^}]*stub\}\}\n?/gi, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; // Fallback in case a different class is passed const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; talkWikitext = talkWikitext.replace(/(\|class\s*=\s*)Stub/i, `$1${newClass}`); await ComradeLib.api.postWithEditToken({ action: 'edit', title: talkName, text: talkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}]*?-stub\}\}\s*\n?/gi; const genericStubRegex = /\{\{stub\}\}\s*/gi; artWikitext = artWikitext.replace(stubRegex, '').replace(genericStubRegex, ''); await ComradeLib.api.postWithEditToken({ action: 'edit', title: pageName, text: artWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}` }); location.reload(); } catch (e) { alert("Promotion failed. Check console for details."); console.error(e); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> manzoomvm3nqq7h6rhyopoxd1se7znm 739844 739837 2026-04-30T07:59:01Z Sam Sailor 26820 Test 739844 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] window.ComradeLib.getOresPrediction = function() { const $el = $('.article_quality'); if (!$el.length) return null; const text = $el.text().trim(); if (/\bFA\b/.test(text)) return 'Fa'; if (/\bGA\b/.test(text)) return 'Ga'; if (/\bB\b/.test(text)) return 'B'; if (/\bC\b/.test(text)) return 'C'; if (/\bStart\b/i.test(text)) return 'Start'; if (/Stub/i.test(text)) return 'Stub'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}]*stub\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); talkClass = classMatch ? classMatch[1].charAt(0).toUpperCase() + classMatch[1].slice(1).toLowerCase() : null; } const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const isHighQuality = ['B', 'Ga', 'Fa'].includes(oresClass); const needsHighQualNudge = isHighQuality && oresClass !== talkClass; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnRead = $('<button>').text('Read').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); }); $content.append(message, $btnRead); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; const newWikitext = wikitext.replace(/\{\{[^}]*stub\}\}\n?/gi, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext === newTalkWikitext) { mw.notify("Talk page already reflects this class."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const cleanArtWikitext = artWikitext.replace(/\{\{[^}]*?-stub\}\}\s*\n?/gi, '').replace(/\{\{stub\}\}\s*/gi, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}` }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed. Check console.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> sxdag6svg5s5ocfzlskqftwvjg8s8kl 739847 739844 2026-04-30T08:05:50Z Sam Sailor 26820 Test 739847 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] window.ComradeLib.getOresPrediction = function() { const $el = $('.article_quality'); if (!$el.length) return null; const text = $el.text().trim(); if (/\bFA\b/.test(text)) return 'Fa'; if (/\bGA\b/.test(text)) return 'Ga'; if (/\bB\b/.test(text)) return 'B'; if (/\bC\b/.test(text)) return 'C'; if (/\bStart\b/i.test(text)) return 'Start'; if (/Stub/i.test(text)) return 'Stub'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}]*stub\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const isHighQuality = ['B', 'Ga', 'Fa'].includes(oresClass); const needsHighQualNudge = isHighQuality && (oresClass !== talkClass); const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnRead = $('<button>').text('Read').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); }); $content.append(message, $btnRead); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; const newWikitext = wikitext.replace(/\{\{[^}]*stub\}\}\n?/gi, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext === newTalkWikitext) { mw.notify("Talk page already reflects this class."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const cleanArtWikitext = artWikitext.replace(/\{\{[^}]*?-stub\}\}\s*\n?/gi, '').replace(/\{\{stub\}\}\s*/gi, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}` }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed. Check console.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> iziw0rambzpp6mdwlkb1ealtuib8rjs 739851 739847 2026-04-30T08:15:33Z Sam Sailor 26820 Test 739851 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] window.ComradeLib.getOresPrediction = function() { const $el = $('.article_quality'); if (!$el.length) return null; const text = $el.text().trim(); if (/\bFA\b/.test(text)) return 'Fa'; if (/\bGA\b/.test(text)) return 'Ga'; if (/\bB\b/.test(text)) return 'B'; if (/\bC\b/.test(text)) return 'C'; if (/\bStart\b/i.test(text)) return 'Start'; if (/Stub/i.test(text)) return 'Stub'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const isHighQuality = ['B', 'Ga', 'Fa'].includes(oresClass); const needsHighQualNudge = isHighQuality && (oresClass !== talkClass); const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnRead = $('<button>').text('Read').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); }); $content.append(message, $btnRead); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; const newWikitext = wikitext.replace(/\{\{[^}]*stub\}\}\n?/gi, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit. Check talk page history.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 3alvyz3yt0re57w92pkoacpbrzgeath 739852 739851 2026-04-30T08:27:01Z Sam Sailor 26820 Test 739852 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] window.ComradeLib.getOresPrediction = function() { const $el = $('.article_quality'); if (!$el.length) return null; const text = $el.text().trim(); if (/\bFA\b/.test(text)) return 'Fa'; if (/\bGA\b/.test(text)) return 'Ga'; if (/\bB\b/.test(text)) return 'B'; if (/\bC\b/.test(text)) return 'C'; if (/\bStart\b/i.test(text)) return 'Start'; if (/Stub/i.test(text)) return 'Stub'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const isHighQuality = ['B', 'Ga', 'Fa'].includes(oresClass); const needsHighQualNudge = isHighQuality && (oresClass !== talkClass); const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnRead = $('<button>').text('Read').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); }); $content.append(message, $btnRead); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> iaugyoj40drhrw6r8uot4exrbr6djjd 739853 739852 2026-04-30T08:36:20Z Sam Sailor 26820 Test 739853 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] window.ComradeLib.getOresPrediction = function() { const $el = $('.article_quality'); if (!$el.length) return null; const text = $el.text().trim(); if (/\bFA\b/.test(text)) return 'Fa'; if (/\bGA\b/.test(text)) return 'Ga'; if (/\bB\b/.test(text)) return 'B'; if (/\bC\b/.test(text)) return 'C'; if (/\bStart\b/i.test(text)) return 'Start'; if (/Stub/i.test(text)) return 'Stub'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnRead = $('<button>').text('Read').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); }); $content.append(message, $btnRead); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> m5dmdzyoh35u7h2x8qwf9ysp3t5yy28 739856 739853 2026-04-30T08:49:46Z Sam Sailor 26820 Test 739856 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] window.ComradeLib.getOresPrediction = function() { const $el = $('.article_quality'); if (!$el.length) return null; const text = $el.text().trim(); if (/\bFA\b/.test(text)) return 'Fa'; if (/\bGA\b/.test(text)) return 'Ga'; if (/\bB\b/.test(text)) return 'B'; if (/\bC\b/.test(text)) return 'C'; if (/\bStart\b/i.test(text)) return 'Start'; if (/Stub/i.test(text)) return 'Stub'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnRead = $('<button>').text('Read').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnRead); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 1goxvru6bh3u95hw760tnkj612e6107 739857 739856 2026-04-30T08:53:40Z Sam Sailor 26820 Test 739857 javascript text/javascript //<nowiki> /* global mw, $, ComradeLib */ /** * Comrade-Lib * Helper functions */ 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, .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; } `); window.ComradeLib = window.ComradeLib || {}; window.ComradeLib.api = new mw.Api(); window.ComradeLib.appendDismiss = function($el) { $('<button>').addClass('comrade-dismiss').text('Dismiss').on('click', function() { $(this).closest('.comrade-container').fadeOut(); }).appendTo($el.find('.comrade-header')); $("#siteSub").after($el); }; // Category audit window.ComradeLib.arrayChunk = function(arr, size) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }; window.ComradeLib.fetchOresData = async function(revIds) { const scores = {}; const BATCH = 1; for (let i = 0; i < revIds.length; i += BATCH) { const rid = revIds[i]; try { const response = await fetch('https://api.wikimedia.org/service/lw/inference/v1/models/enwiki-articlequality:predict', { method: 'POST', body: JSON.stringify({ rev_id: parseInt(rid, 10) }) }); if (!response.ok) { console.warn(`Comrade: Lift Wing ${response.status} for rev ${rid}`); continue; } const data = await response.json(); const prediction = data?.enwiki?.scores?.[String(rid)]?.articlequality?.score?.prediction ?? data?.output?.prediction ?? null; if (prediction) scores[String(rid)] = prediction; } catch (e) { console.warn(`Comrade: Lift Wing fetch error for rev ${rid}`, e); } } return scores; }; window.ComradeLib.performCategoryAudit = async function() { const api = new mw.Api(); const $links = $('#mw-pages li a'); const titles = $links.map((i, el) => $(el).attr('title')).get().filter(t => t && !t.startsWith('Talk:')); if (!titles.length) return; const $btn = $('#comrade-audit-btn'); $btn.prop('disabled', true).text('Auditing...'); const chunks = ComradeLib.arrayChunk(titles, 25); const results = {}; try { for (const chunk of chunks) { const talkTitles = chunk.map(t => 'Talk:' + t); const res = await api.get({ action: 'query', titles: chunk.concat(talkTitles).join('|'), prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', formatversion: 2 }); const pages = res.query.pages; const getContent = pg => { if (!pg || pg.missing || !pg.revisions) return ''; const rev = pg.revisions[0]; return rev.slots?.main?.content ?? rev.content ?? ''; }; const revIds = []; const titleToRevId = {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); if (mainPg?.revisions?.[0]?.revid) { const rid = String(mainPg.revisions[0].revid); revIds.push(rid); titleToRevId[title] = rid; } }); const oresScores = revIds.length ? await ComradeLib.fetchOresData(revIds) : {}; chunk.forEach(title => { const mainPg = pages.find(p => p.title.toLowerCase() === title.toLowerCase() && !p.missing); const talkPg = pages.find(p => p.title.toLowerCase() === ('talk:' + title).toLowerCase() && !p.missing); const talkContent = getContent(talkPg); const mainContent = getContent(mainPg); const classMatch = talkContent.match(/\|\s*class\s*=\s*([^|{}\n\r]+)/i); const rawClass = classMatch ? classMatch[1].trim() : ''; const talkClass = rawClass ? rawClass.charAt(0).toUpperCase() + rawClass.slice(1).toLowerCase() : 'Unrated'; const hasStubTags = /\{\{[^}]*stub[^}]*\}\}/i.test(mainContent); const rid = titleToRevId[title]; const predicted = rid ? (oresScores[rid] ?? null) : null; results[title] = { talkClass, predicted, hasStubTags }; }); } Object.entries(results).forEach(([title, { talkClass, predicted, hasStubTags }]) => { ComradeLib.renderCategoryAuditUI(title, talkClass, predicted, hasStubTags); }); $btn.text('Audit complete').fadeOut(2000); } catch (error) { console.error('Comrade: Audit error', error); $btn.text('Audit failed').prop('disabled', false); } }; window.ComradeLib.renderCategoryAuditUI = function(title, talkClass, predicted, hasStub) { const $link = $(`#mw-pages li a[title="${title}"]`); if (!$link.length) return; let label = ''; let style = 'font-size: 0.85em; margin-left: 8px; font-weight: normal;'; if (talkClass !== 'Stub' && talkClass !== 'Unrated' && hasStub) { label = `${talkClass} (stub tag present) : ORES: ${predicted || '?'}`; style += ' color: #f0ad4e;'; } else if (talkClass === 'Stub' && predicted && predicted !== 'Stub') { label = `Stub : ${predicted}`; style += ' color: #27ae60;'; } else if (predicted && predicted !== talkClass) { label = `${talkClass} : ${predicted}`; style += ' color: #36c;'; } if (label) { $link.parent().find('.comrade-audit-label').remove(); const $span = $('<span>').addClass('comrade-audit-label').attr('style', style).text(` [${label}]`); $link.after($span); } }; // Category audit END // Deorphanizer // performDeorphan window.ComradeLib.performDeorphan = async function() { try { await ComradeLib.api.edit(mw.config.get('wgPageName'), function(rev) { let text = rev.content; const summary = 'Article has backlinks; removed [[Template:Orphan|{{Orphan}}]]'; text = text.replace(/\{\{Orphan\s*(?:\|[^}]*)?\}\}\s*/gi, ''); text = text.replace(/(\{\{Multiple[ _]issues\s*)\|\s*\|/gi, '$1|'); text = ComradeLib.replaceMI(text); text = text.replace(/\n{3,}/g, '\n\n').trim(); return { text, summary, minor: true }; }); mw.notify('Orphan tag removed!', { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to remove orphan tag.', { type: 'error' }); } }; // replaceMI (Multiple Issues) window.ComradeLib.replaceMI = function(text) { const miRegex = /\{\{Multiple[ _]issues\s*\|/gi; let result = ''; let lastIndex = 0; let match; while ((match = miRegex.exec(text)) !== null) { const start = match.index; result += text.slice(lastIndex, start); let depth = 0; let i = start; while (i < text.length) { if (text[i] === '{' && text[i + 1] === '{') { depth++; i += 2; } else if (text[i] === '}' && text[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } const end = i; const full = text.slice(start, end); const pipeIdx = full.indexOf('|'); const inner = full.slice(pipeIdx + 1, full.length - 2).trim(); const topLevel = ComradeLib.extractTopLevelTemplates(inner); if (topLevel.length === 0) { result += ''; } else if (topLevel.length === 1) { result += topLevel[0].trim(); } else { result += full; } lastIndex = end; } result += text.slice(lastIndex); return result; }; // extractTopLevelTemplates window.ComradeLib.extractTopLevelTemplates = function(inner) { const templates = []; let i = 0; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { let depth = 0; const start = i; while (i < inner.length) { if (inner[i] === '{' && inner[i + 1] === '{') { depth++; i += 2; } else if (inner[i] === '}' && inner[i + 1] === '}') { depth--; i += 2; if (depth === 0) break; } else { i++; } } templates.push(inner.slice(start, i)); } else { i++; } } return templates; }; // Quality suite // getOresPrediction // Dependency: [[m:User:EpochFail/ArticleQuality-system.js]] window.ComradeLib.getOresPrediction = function() { const $el = $('.article_quality'); if (!$el.length) return null; const text = $el.text().trim(); if (/\bFA\b/.test(text)) return 'Fa'; if (/\bGA\b/.test(text)) return 'Ga'; if (/\bB\b/.test(text)) return 'B'; if (/\bC\b/.test(text)) return 'C'; if (/\bStart\b/i.test(text)) return 'Start'; if (/Stub/i.test(text)) return 'Stub'; return null; }; // checkQualityConsistency window.ComradeLib.checkQualityConsistency = async function() { const oresClass = ComradeLib.getOresPrediction(); if (!oresClass || oresClass === 'Stub') return; const talkTitle = 'Talk:' + mw.config.get('wgPageName'); try { const [talkRes, pageRes] = await Promise.all([ ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkTitle, rvprop: 'content', formatversion: 2 }), ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }) ]); const talkPage = talkRes.query.pages[0]; const mainWikitext = pageRes.query.pages[0].revisions[0].content; const hasStubTag = /\{\{[^}|]*stub[^}]*\}\}/i.test(mainWikitext); let talkClass = null; if (!talkPage.missing && talkPage.revisions) { const classMatch = talkPage.revisions[0].content.match(/\|class\s*=\s*([^|}\s]+)/i); if (classMatch) { const rawClass = classMatch[1].toLowerCase(); if (rawClass === 'ga') talkClass = 'Ga'; else if (rawClass === 'fa') talkClass = 'Fa'; else talkClass = rawClass.charAt(0).toUpperCase() + rawClass.slice(1); } } const isStubRated = talkClass === 'Stub'; const isStartRated = talkClass === 'Start'; const needsPromotionNudge = (isStubRated && (oresClass === 'Start' || oresClass === 'C')) || (isStartRated && oresClass === 'C'); const needsHighQualNudge = (oresClass !== talkClass) && !needsPromotionNudge; const lingeringStub = (talkClass && !isStubRated && hasStubTag); if (talkPage.missing || needsHighQualNudge || needsPromotionNudge || lingeringStub) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $content = $('<div>').append($('<strong>').text('Quality:'), " "); let message = `ORES suggests <b>${oresClass}</b>. `; if (talkPage.missing) { message = `Talk page missing; ORES suggests <b>${oresClass}</b>. `; const $btnRater = $('<button>').text('Open Rater').on('click', () => $('#ca-rater, #t-rater').find('a')[0].click()); $content.append(message, $btnRater); } else if (needsHighQualNudge) { message = `ORES predicts <b>${oresClass}</b>. Please assess manually. `; const $btnOkay = $('<button>').text('OK').on('click', () => { window.location.hash = "#content"; mw.notify("Review the criteria for " + oresClass + "-class."); $container.find('.comrade-dismiss').click(); }); $content.append(message, $btnOkay); } else if (lingeringStub && !needsPromotionNudge) { message = `Article is ${talkClass}-class, but stub tags remain. `; const $btnRemove = $('<button>').text('Remove stub tags').on('click', () => ComradeLib.performStubRemoval()); $content.append(message, $btnRemove); } else if (needsPromotionNudge) { const $btnTarget = $('<button>').text(`Promote to ${oresClass}`).on('click', () => ComradeLib.performPromotion(oresClass)); $content.append(message, $btnTarget); if (isStubRated && oresClass === 'C') { const $btnStart = $('<button>').text(`Promote to Start`).on('click', () => ComradeLib.performPromotion('Start')); $content.append("\u00A0or\u00A0", $btnStart); } } $container.append($('<div>').addClass('comrade-header').append($content)); $('#content').prepend($container); ComradeLib.appendDismiss($container); } } catch (e) { console.error("Comrade quality check failed", e); } }; // performStubRemoval window.ComradeLib.performStubRemoval = async function() { try { const pageData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: mw.config.get("wgPageName"), rvprop: 'content', formatversion: 2 }); let wikitext = pageData.query.pages[0].revisions[0].content; // 'g' flag is appropriate here for replacement const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const newWikitext = wikitext.replace(stubRegex, '').trim(); if (wikitext === newWikitext) { mw.notify("No stub tags found to remove."); return; } await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: mw.config.get("wgPageName"), text: newWikitext, summary: `Removing lingering stub tags per ORES/Talk assessment`, nocreate: true }); location.reload(); } catch (err) { console.error("Comrade failed to remove stub tags:", err); mw.notify("Error removing stub tags.", { type: 'error' }); } }; // performPromotion window.ComradeLib.performPromotion = async function(newClass) { const classLinks = { 'Start': '[[WP:STARTCLASS|Start-Class]]', 'C': '[[WP:CCLASS|C-Class]]', 'B': '[[WP:BCLASS|B-Class]]' }; const linkedClass = classLinks[newClass] || newClass; const pageName = mw.config.get('wgPageName'); const talkName = 'Talk:' + pageName; try { const talkData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: talkName, rvprop: 'content', formatversion: 2 }); let talkWikitext = talkData.query.pages[0].revisions[0].content; const newTalkWikitext = talkWikitext.replace(/(\|class\s*=\s*)([^|}\s]+)/i, `$1${newClass}`); if (talkWikitext !== newTalkWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: talkName, text: newTalkWikitext, summary: `Promoting article to ${linkedClass} per review and [[WP:ORES]]` }); } const artData = await ComradeLib.api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', formatversion: 2 }); let artWikitext = artData.query.pages[0].revisions[0].content; const stubRegex = /\{\{[^}|]*stub[^}]*\}\}\s*\n?/gi; const cleanArtWikitext = artWikitext.replace(stubRegex, ''); if (artWikitext !== cleanArtWikitext) { await ComradeLib.api.postWithToken('csrf', { action: 'edit', title: pageName, text: cleanArtWikitext, summary: `Removing stub tags; article promoted to ${linkedClass}`, nocreate: true }); } location.reload(); } catch (e) { console.error("Promotion failed:", e); mw.notify("Promotion failed during multi-step edit.", { type: 'error' }); } }; // Quality suite END // Redirects // checkAndRenderRedirect window.ComradeLib.checkAndRenderRedirect = async function(redirTitle, targetTitle, rCategory, labelText, customWikitext) { const res = await ComradeLib.api.get({ action: 'query', titles: redirTitle, formatversion: 2 }); if (res.query.pages[0].missing) { const safeId = "comrade-redir-" + redirTitle.replace(/[^a-z0-9]/gi, '-'); const $container = $('<div>').addClass('comrade-container comrade-info').attr('id', safeId); const $header = $('<div>').addClass('comrade-header'); const summary = `Creating redirect to [[${targetTitle}]] (${labelText.toLowerCase()})`; const $btn = $('<button>').text('Create redirect').on('click', () => ComradeLib.createModernRedirect(redirTitle, targetTitle, rCategory, summary, safeId, customWikitext)); $header.append($('<div>').append($('<strong>').text(`${labelText}: `), `Create redirect from `, $('<code>').text(redirTitle), $btn)); $container.append($header); ComradeLib.appendDismiss($container); } }; // createModernRedirect window.ComradeLib.createModernRedirect = async function(redirTitle, targetTitle, rCategory, customSummary, elementId, customWikitext) { const api = ComradeLib.api; const content = customWikitext || `#REDIRECT [[${targetTitle}]]\n\n{{Redirect category shell|\n{{${rCategory}}}\n}}`; const summary = customSummary || `Creating redirect to [[${targetTitle}]]`; try { await api.postWithEditToken({ action: 'edit', title: redirTitle, text: content, summary: summary, createonly: true }); mw.notify("Redirect created!", { type: 'success', tag: 'comrade', classes: ['comrade-success'] }); $(`#${elementId}`).fadeOut(); } catch (e) { mw.notify("Failed to create redirect.", { type: 'error' }); } }; // Domain name window.ComradeLib.checkDomainRedirect = async function(qid, currentTitle) { let domainCandidate = ""; if (qid) { try { const url = new URL("https://www.wikidata.org/w/api.php"); url.search = new URLSearchParams({ action: 'wbgetclaims', entity: qid, property: 'P856', format: 'json', origin: '*' }); const res = await fetch(url).then(r => r.json()); if (res.claims?.P856) { domainCandidate = res.claims.P856[0].mainsnak.datavalue.value; } } catch (e) { console.warn("Comrade: Wikidata API call failed."); } } if (!domainCandidate) { domainCandidate = $(".infobox .url a").last().attr("href") || $(".official-website a").first().attr("href"); } if (domainCandidate) { try { const url = new URL(domainCandidate); let domain = url.hostname.replace(/^www\d*\./, ""); if (domain.includes(".") && url.pathname === "/") { const citationURL = '/api/rest_v1/data/citation/mediawiki/' + encodeURIComponent(domainCandidate); try { const response = await fetch(citationURL); if (response.ok) { await ComradeLib.checkAndRenderRedirect(domain, currentTitle, "R from domain name", "Domain"); } } catch (e) { console.warn("Comrade: Citation API check failed."); } } } catch (e) { console.warn("Comrade: Error parsing URL object."); } } }; // Long name window.ComradeLib.getLongNameFromWikitext = function(wikitext) { const firstLine = wikitext.split('\n').find(l => l.includes("'''")); if (!firstLine) return null; const boldMatch = firstLine.match(/'''(.+?)'''/); if (!boldMatch) return null; return boldMatch[1].replace(/\s*([“"«《„‹「『'].+?[”"»》”›」』']|\([^)\n]*\))\s*/g, ' ').replace(/\s+/g, ' ').trim(); }; // Expanded normalization window.ComradeLib.getNormalizedTitles = function(currentTitle) { const maps = { all: { 'æ': 'ae', 'Æ': 'Ae', 'œ': 'oe', 'Œ': 'Oe', 'ij': 'ij', 'IJ': 'Ij', 'ß': 'ss', 'ẞ': 'SS', 'ð': 'd', 'Ð': 'D', 'ø': 'o', 'Ø': 'O', 'ł': 'l', 'Ł': 'L', 'þ': 'th', 'Þ': 'Th', 'ŋ': 'ng', 'Ŋ': 'Ng' } }; let cleanAscii = currentTitle.split('').map((char, index) => { let rep = maps.all[char]; if (rep) { return (index > 0 && rep.length > 1) ? rep.toLowerCase() : rep; } return char; }).join(''); cleanAscii = cleanAscii.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); return { ascii: (cleanAscii.toLowerCase() !== currentTitle.toLowerCase()) ? cleanAscii : null }; }; // Redirects END // SD window.ComradeLib.checkShortDescDates = async function(wikitext, entity) { const sdMatch = wikitext.match(/\{\{[Ss]hort description\|([^|{}]+)(?:\|[^}]*)?\}\}/); if (!sdMatch) return; const currentSD = sdMatch[1].trim(); const dateRegex = /\((?:born |died |fl\. )?\d{3,4}(?:–\d{3,4}|s)?\)/i; const hasDate = dateRegex.test(currentSD); if (hasDate && currentSD.length > 15) return; const getYear = (val) => { if (!val || !val.time) return null; const yearMatch = val.time.match(/[+-](\d{4,})/); if (!yearMatch) return null; let year = parseInt(yearMatch[1]); if (val.time.startsWith('-')) return year + " BC"; if (val.precision === 8) return year + "s"; return year; }; let dateStr = ""; const bYear = getYear(entity.claims?.P569?.[0]?.mainsnak?.datavalue?.value); const dYear = getYear(entity.claims?.P570?.[0]?.mainsnak?.datavalue?.value); if (bYear && dYear) dateStr = `(${bYear}–${dYear})`; else if (bYear) dateStr = `(born ${bYear})`; else if (dYear) dateStr = `(died ${dYear})`; else { const flYear = getYear(entity.claims?.P1317?.[0]?.mainsnak?.datavalue?.value); if (flYear) dateStr = `(fl. ${flYear})`; } if (!dateStr) return; let finalSD = currentSD; const words = currentSD.split(' '); if (words.length === 1 && !hasDate) { const occupationId = entity.claims?.P106?.[0]?.mainsnak?.datavalue?.value?.id; if (occupationId) { const occRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${occupationId}&props=labels&languages=en&format=json&origin=*`).then(r => r.json()); const occLabel = occRes.entities[occupationId]?.labels?.en?.value; if (occLabel) { finalSD = `${occLabel.charAt(0).toLowerCase() + occLabel.slice(1)} ${dateStr}`; } } else { finalSD = `${currentSD} ${dateStr}`; } } else if (!hasDate) { finalSD = `${currentSD} ${dateStr}`; } if (finalSD !== currentSD) { ComradeLib.renderSDNudge(currentSD, finalSD); } }; window.ComradeLib.renderSDNudge = function(oldSD, newSD) { const $container = $('<div>').addClass('comrade-container comrade-nudge'); const $header = $('<div>').addClass('comrade-header').append($('<strong>').text('Short Description:')); const $content = $('<div>').addClass('comrade-content').css({ 'flex-direction': 'row', 'align-items': 'center', 'flex-wrap': 'wrap', 'gap': '10px' }); $content.append($('<span>').text('Dates missing: '), $('<code>').text(newSD)); const $btn = $('<button>').text('Update').on('click', async function() { const api = new mw.Api(); try { const title = mw.config.get("wgPageName"); const res = await api.get({ action: 'query', prop: 'revisions', titles: title, rvprop: 'content', formatversion: 2 }); let content = res.query.pages[0].revisions[0].content; content = content.replace(/(\{\{[Ss]hort description\|)[^|}]+/, `$1${newSD}`); await api.postWithEditToken({ action: 'edit', title: title, text: content, summary: `Updating short description: +dates/role per [[WP:SDFORMAT]]`, nocreate: true }); mw.notify('Short description updated!'); setTimeout(() => location.reload(), 700); } catch (e) { mw.notify('Failed to update SD.', { type: 'error' }); } }); $content.append($btn); $container.append($header, $content); ComradeLib.appendDismiss($container); }; //</nowiki> 36zbfhn6yi1jx3di0uyf78ara4d3407 Wikipedia:ArticleGuidance 4 175052 739764 2026-04-29T12:27:59Z SBisson (WMF) 29010 Created page with "Welcome! This is a test page [https://www.mediawiki.org/wiki/Article_guidance Article Guidance project]. <big> '''What to do?''' * '''[[Special:NewArticle|Start a new article]]''' using Article Guidance. * '''[[:Category:Pages_using_article_guidance|Review the outlines]]''' that encapsulate the guidance provided for each type of article. </big> Try creating articles, improving the outlines, and sharing your thoughts [https://www.mediawiki.org/wiki/Talk:Article..." 739764 wikitext text/x-wiki Welcome! This is a test page [https://www.mediawiki.org/wiki/Article_guidance Article Guidance project]. <big> '''What to do?''' * '''[[Special:NewArticle|Start a new article]]''' using Article Guidance. * '''[[:Category:Pages_using_article_guidance|Review the outlines]]''' that encapsulate the guidance provided for each type of article. </big> Try creating articles, improving the outlines, and sharing your thoughts [https://www.mediawiki.org/wiki/Talk:Article_guidance on the project talk page]. In particular, we want to learn about: * How the new experience compares to your current experience creating new articles. * How effective was the assistance for a particular type of article, and which improvements you made/proposed for the outline of a specific type of article. For more details check the [https://www.mediawiki.org/wiki/Article_guidance/Test_feature_guide quick test guide] or the sections below. == Creating articles == You can [[Special:NewArticle|create any article]] or use the example list of missing articles below to jump to the Article Guidance workflow: * [[Charles Chaplin]] (actor) * [[The Dark side of the Moon]] (album) * [[Caracas]] (city) * [[Nauru]] (country) * [[Dog]] (animal) * [[Michael Jordan]] (sportsperson) * [[Mercury]] (chemical element) * [[Bob Marley]] (musician) * [[Influenza]] (disease) For an overview of the process, you can check [https://commons.wikimedia.org/wiki/File:Article_Guidance_workflow_demo_-_April_2026.webm this demo]. hiparn2mbwo2zogvqeul69gm1opv6zz 739765 739764 2026-04-29T12:30:43Z SBisson (WMF) 29010 739765 wikitext text/x-wiki Welcome! This is a test page [https://www.mediawiki.org/wiki/Article_guidance Article Guidance project]. <big> '''What to do?''' * '''[[Special:NewArticle|Start a new article]]''' using Article Guidance. * '''[[:Category:Pages_using_article_guidance|Review the outlines]]''' that encapsulate the guidance provided for each type of article. </big> Try creating articles, improving the outlines, and sharing your thoughts [https://www.mediawiki.org/wiki/Talk:Article_guidance on the project talk page]. In particular, we want to learn about: * How the new experience compares to your current experience creating new articles. * How effective was the assistance for a particular type of article, and which improvements you made/proposed for the outline of a specific type of article. For more details check the [https://www.mediawiki.org/wiki/Article_guidance/Test_feature_guide quick test guide] or the sections below. == Creating articles == You can [[Special:NewArticle|create any article]] or use the example list of missing articles below to jump to the Article Guidance workflow: * [[Charles Chaplin]] (actor) * [[The Dark side of the Moon]] (album) * [[Caracas]] (city) * [[Nauru]] (country) * [[Dog]] (animal) * [[Michael Jordan]] (sportsperson) * [[Mercury]] (chemical element) * [[Bob Marley]] (musician) * [[Influenza]] (disease) For an overview of the process, you can check [https://commons.wikimedia.org/wiki/File:Article_Guidance_workflow_demo_-_April_2026.webm this demo]. == Article Wizard == Some wikis have a community-created article wizard that guide the user through documentation pages before landing on the editor. The Article Guidance experiment can redirect some of the traffic from the [[Wikipedia:ArticleWizard]] to the Article Guidance workflow. 542ulf3ptpe3q5c2glbeikvfxtfu2cr 739767 739765 2026-04-29T12:39:09Z SBisson (WMF) 29010 739767 wikitext text/x-wiki Welcome! This is a test page [https://www.mediawiki.org/wiki/Article_guidance Article Guidance project]. <big> '''What to do?''' * '''[[Special:NewArticle|Start a new article]]''' using Article Guidance. * '''[[:Category:Pages_using_article_guidance|Review the outlines]]''' that encapsulate the guidance provided for each type of article. </big> Try creating articles, improving the outlines, and sharing your thoughts [https://www.mediawiki.org/wiki/Talk:Article_guidance on the project talk page]. In particular, we want to learn about: * How the new experience compares to your current experience creating new articles. * How effective was the assistance for a particular type of article, and which improvements you made/proposed for the outline of a specific type of article. For more details check the [https://www.mediawiki.org/wiki/Article_guidance/Test_feature_guide quick test guide] or the sections below. == Creating articles == You can [[Special:NewArticle|create any article]] or use the example list of missing articles below to jump to the Article Guidance workflow: * [[Charles Chaplin]] (actor) * [[The Dark side of the Moon]] (album) * [[Dublin]] (city) * [[Nauru]] (country) * [[Lemur]] (animal) * [[Michael Jordan]] (sportsperson) * [[Silver]] (chemical element) * [[Bob Marley]] (musician) * [[Influenza]] (disease) For an overview of the process, you can check [https://commons.wikimedia.org/wiki/File:Article_Guidance_workflow_demo_-_April_2026.webm this demo]. == Article Wizard == Some wikis have a community-created article wizard that guide the user through documentation pages before landing on the editor. The Article Guidance experiment can redirect some of the traffic from the [[Wikipedia:ArticleWizard]] to the Article Guidance workflow. oxnn5ikawai7j7qhjhkxhyck8hgoobz 739768 739767 2026-04-29T12:39:30Z SBisson (WMF) 29010 739768 wikitext text/x-wiki Welcome! This is a test page [https://www.mediawiki.org/wiki/Article_guidance Article Guidance project]. <big> '''What to do?''' * '''[[Special:NewArticle|Start a new article]]''' using Article Guidance. * '''[[:Category:Pages_using_article_guidance|Review the outlines]]''' that encapsulate the guidance provided for each type of article. </big> Try creating articles, improving the outlines, and sharing your thoughts [https://www.mediawiki.org/wiki/Talk:Article_guidance on the project talk page]. In particular, we want to learn about: * How the new experience compares to your current experience creating new articles. * How effective was the assistance for a particular type of article, and which improvements you made/proposed for the outline of a specific type of article. For more details check the [https://www.mediawiki.org/wiki/Article_guidance/Test_feature_guide quick test guide] or the sections below. == Creating articles == You can [[Special:NewArticle|create any article]] or use the example list of missing articles below to jump to the Article Guidance workflow: * [[Charles Chaplin]] (actor) * [[The Dark side of the Moon]] (album) * [[Dublin]] (city) * [[Nauru]] (country) * [[Lemur]] (animal) * [[Michael Jordan]] (sportsperson) * [[Nitrogen]] (chemical element) * [[Bob Marley]] (musician) * [[Influenza]] (disease) For an overview of the process, you can check [https://commons.wikimedia.org/wiki/File:Article_Guidance_workflow_demo_-_April_2026.webm this demo]. == Article Wizard == Some wikis have a community-created article wizard that guide the user through documentation pages before landing on the editor. The Article Guidance experiment can redirect some of the traffic from the [[Wikipedia:ArticleWizard]] to the Article Guidance workflow. 185c9n0pxvbrf3xmyjqqfya2psoyvxn 739769 739768 2026-04-29T12:39:49Z SBisson (WMF) 29010 739769 wikitext text/x-wiki Welcome! This is a test page [https://www.mediawiki.org/wiki/Article_guidance Article Guidance project]. <big> '''What to do?''' * '''[[Special:NewArticle|Start a new article]]''' using Article Guidance. * '''[[:Category:Pages_using_article_guidance|Review the outlines]]''' that encapsulate the guidance provided for each type of article. </big> Try creating articles, improving the outlines, and sharing your thoughts [https://www.mediawiki.org/wiki/Talk:Article_guidance on the project talk page]. In particular, we want to learn about: * How the new experience compares to your current experience creating new articles. * How effective was the assistance for a particular type of article, and which improvements you made/proposed for the outline of a specific type of article. For more details check the [https://www.mediawiki.org/wiki/Article_guidance/Test_feature_guide quick test guide] or the sections below. == Creating articles == You can [[Special:NewArticle|create any article]] or use the example list of missing articles below to jump to the Article Guidance workflow: * [[Charles Chaplin]] (actor) * [[The Dark side of the Moon]] (album) * [[Dublin]] (city) * [[Nauru]] (country) * [[Lemur]] (animal) * [[Michael Jordan]] (sportsperson) * [[Magnesium]] (chemical element) * [[Bob Marley]] (musician) * [[Influenza]] (disease) For an overview of the process, you can check [https://commons.wikimedia.org/wiki/File:Article_Guidance_workflow_demo_-_April_2026.webm this demo]. == Article Wizard == Some wikis have a community-created article wizard that guide the user through documentation pages before landing on the editor. The Article Guidance experiment can redirect some of the traffic from the [[Wikipedia:ArticleWizard]] to the Article Guidance workflow. gfb9ubvtslt45rd8vju4xkn2iwelhn6 739770 739769 2026-04-29T12:39:59Z SBisson (WMF) 29010 739770 wikitext text/x-wiki Welcome! This is a test page [https://www.mediawiki.org/wiki/Article_guidance Article Guidance project]. <big> '''What to do?''' * '''[[Special:NewArticle|Start a new article]]''' using Article Guidance. * '''[[:Category:Pages_using_article_guidance|Review the outlines]]''' that encapsulate the guidance provided for each type of article. </big> Try creating articles, improving the outlines, and sharing your thoughts [https://www.mediawiki.org/wiki/Talk:Article_guidance on the project talk page]. In particular, we want to learn about: * How the new experience compares to your current experience creating new articles. * How effective was the assistance for a particular type of article, and which improvements you made/proposed for the outline of a specific type of article. For more details check the [https://www.mediawiki.org/wiki/Article_guidance/Test_feature_guide quick test guide] or the sections below. == Creating articles == You can [[Special:NewArticle|create any article]] or use the example list of missing articles below to jump to the Article Guidance workflow: * [[Charles Chaplin]] (actor) * [[The Dark side of the Moon]] (album) * [[Dublin]] (city) * [[Nauru]] (country) * [[Lemur]] (animal) * [[Michael Jordan]] (sportsperson) * [[Bob Marley]] (musician) * [[Influenza]] (disease) For an overview of the process, you can check [https://commons.wikimedia.org/wiki/File:Article_Guidance_workflow_demo_-_April_2026.webm this demo]. == Article Wizard == Some wikis have a community-created article wizard that guide the user through documentation pages before landing on the editor. The Article Guidance experiment can redirect some of the traffic from the [[Wikipedia:ArticleWizard]] to the Article Guidance workflow. kptliw7nhmyvz053va295zgsd5k9qwj Wikipedia:ArticleWizard 4 175053 739766 2026-04-29T12:31:08Z SBisson (WMF) 29010 Created page with "Placeholder for a community-created article wizard." 739766 wikitext text/x-wiki Placeholder for a community-created article wizard. pa48mukewnu5dkxpiakso84b0qk9e8k User:SBisson (WMF)/test 2 175054 739774 2026-04-29T14:15:17Z SBisson (WMF) 29010 Created page with "Here's a link to the [[Wikipedia:ArticleWizard]]" 739774 wikitext text/x-wiki Here's a link to the [[Wikipedia:ArticleWizard]] ax2c45995caq3mkkg23waaivi60184s User:MrJaroslavik/common.js 2 175055 739775 2026-04-29T14:24:01Z MrJaroslavik 44012 + 739775 javascript text/javascript /* <nowiki> */ /** * GCUS Data Generator v6.9.2 * Fixed: Syntax error on line 164 (Optional Chaining removal) */ (function() { if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || mw.config.get('wgTitle').split('/').pop() !== 'GCUS-Data') { return; } const USERS_LIST = `.anaconda@kvwiki .anaconda@tiwiki .mau.@itwiki 0x010C@frwiki 1234qwer1234qwer4@loginwiki 1997kB@wikidatawiki 3(MG)²@frwiki A09@loginwiki A09@slwiki ABX@plwiki AJones (WMF)@enwiki AJones (WMF)@metawiki AKarani (WMF)@enwiki AKoval (WMF)@enwiki ATripathi-WMF@enwiki ATripathi-WMF@eswiki Aaron Schulz@enwiki Abisys@itwiki Acagastya@commonswiki Acagastya@enwikinews Adailton@ptwiki AdiJapan@rowiki Adler.fa@fawiki Adrian@skwiki Adrignola@enwikibooks Agony@fiwiki Ajraddatz@commonswiki Ajraddatz@enwiki Ajraddatz@eswiki Ajraddatz@loginwiki Ajraddatz@metawiki Akeron@frwiki Akoopal@nlwiki Alain r@frwiki Alan@commonswiki Alan@eswiki Alan@metawiki Albertoleoncio@loginwiki Albertoleoncio@ptwiki Aldnonymous@idwiki Alex Shih@enwiki Alexander Misel@zhwiki Alexanderps@ptwiki Alexanderps@ptwikinews Alhen@eswiki Alison@enwiki Alnokta@arwiki Alphax@commonswiki Alraunenstern@dewiki Amalthea@enwiki AmandaNP@enwiki AmandaNP@hrwiki AmandaNP@itwiki AmandaNP@loginwiki AmandaNP@wikidatawiki Andre Engels@acewiki Andre Engels@anwiki Andre Engels@barwiki Andre Engels@bgwiki Andre Engels@bswiki Andre Engels@dewiki Andre Engels@eswiktionary Andre Engels@fawiki Andre Engels@fawikiquote Andre Engels@kowiki Andre Engels@lvwiki Andre Engels@mswiki Andre Engels@nlwiki Andre Engels@ptwiki Andre Engels@ptwiktionary Andre Engels@sourceswiki Andre Engels@specieswiki Andre Engels@testwiki Andre Engels@urwiki Andre Engels@viwiki Andre Engels@zeawiki Andre Engels@zhwiki Andrei Stroe@rowiki Andrej Šalov@hrwiki Andriy.v@ukwiki Annabel@nlwiki Anr@fiwiki Anthere@enwiki Anthere@metawiki AntiCompositeNumber@loginwiki Antime@arwiki Aoidh@enwiki Aoineko@frwiki Aphaia@enwikiquote AramilFeraxa@loginwiki AramilFeraxa@metawiki AramilFeraxa@plwiki Aratal@frwiki Arcticocean@enwiki Arcticocean@hrwiki Arcticocean@kowiki Arcticocean@ptwiki Arcticocean@ukwiki Argo Navis@hrwiki Ash Crow@frwiki Asilvering@enwiki Ask21@itwiki Ausir@plwiki Avocato@arwiki Avraham@enwiki Avraham@loginwiki Azafred@enwiki B20180@thwiki BDD@enwiki BRPever@loginwiki BRPever@simplewiki BRPever@wikidatawiki Bahamut0013@enwiki Barak a@hewiki Barcex@eswiki Barkeep49@enwiki Barras@enwiki Barras@loginwiki Barras@metawiki Barras@simplewiki Base@loginwiki Bastique@azwiki Bastique@commonswiki Bastique@crwiki Bastique@enwiki Bastique@enwikibooks Bastique@enwikiquote Bastique@enwikiversity Bastique@eswiki Bastique@nowiki Bastique@plwiki Bastique@ruwiki Bastique@simplewiki Bastique@stwiki Bastique@svwiki Bastique@testwiki Bastique@trwiki Bastique@wikimania2008wiki Bastique@yiwiki Bastique@zhwiki Bbb23@enwiki Bdk@dewiki Beeblebrox@enwiki Bellcricket@jawiki Bencmq@zhwiki Benevolent Otter@plwiki Bennylin@idwiki Benoît Prieur@frwiki Berean Hunter@enwiki Beria@ptwiki Bernard@eswiki BetoCG@eswiki Billinghurst@enwikisource Billinghurst@eswikivoyage Billinghurst@loginwiki Billinghurst@metawiki Billinghurst@plwikinews Billinghurst@slwiki Bináris2@huwiki Bináris@huwiki Biologo32@ptwiki Blablubbs@enwiki BlackBeast@eswiki Borgx@idwiki Bradv@enwiki Brandon@enwiki BraneJ@srwiki Brian McNeil@enwikinews Brian@enwikinews Brooke Vibber@dewiki Brooke Vibber@enwiki Brooke Vibber@frwiki Brooke Vibber@mediawikiwiki Brooke Vibber@testwiki Bryan@commonswiki BryanDavis@labswiki Bsadowski1@loginwiki Bsadowski1@metawiki Bsadowski1@simplewiki CAshraf (WMF)@enwiki CLo (WMF)@testwiki CMaslak (WMF)@enwiki CMaslak (WMF)@fawiki CMaslak (WMF)@srwiki CMaslak (WMF)@uzwiki CPettet (WMF)@labswiki CSteigenberger (WMF)@cawiki CSteigenberger (WMF)@enwiki CSteigenberger (WMF)@jawiki CSteigenberger (WMF)@metawiki CSteipp (WMF)@ptwiktionary Cabayi@enwiki Caesar@svwiki Callanecc@enwiki CaptainEek@enwiki Carkuni@jawiki Cartman02au@metawiki Cary Bass@arwiki Cary Bass@aswiki Cary Bass@commonswiki Cary Bass@enwiki Cary Bass@enwikiquote Cary Bass@enwikiversity Cary Bass@frwiki Cary Bass@hiwiki Cary Bass@itwiki Cary Bass@metawiki Cary Bass@ptwiki Casliber@enwiki Cassiopeia sweet@jawiki Cato@enwikiquote Cdip150@zhwiki Cgt@dawiki Chaos@arwiki Chase me ladies, I'm the Cavalry@enwiki Chatama@jawiki Christian List@dawiki Christine (WMF)@enwiki Chuck Entz@enwiktionary Cinabrium@eswiki Ciphers@arwiki Cirdan@dewiki Cirt@enwikinews Civvi~itwiki@itwiki Clem23@frwiki Codc@dewiki CodeMonk@ruwiki Coet@cawiki Conde Edmond Dantès@ptwiki Connel MacKenzie@enwiktionary Cool Hand Luke@enwiki Coren@enwiki Coren@labswiki Count Count@dewiki Count Count@loginwiki Courcelles@enwiki Creol@simplewiki Cromium@elwiki Cromium@enwikinews Cromium@loginwiki Cromium@rowiki Cromium@sqwiki Cromium@zhwiki Cruccone@itwiki Csigabi@huwiki Cspurrier@enwikinews Cspurrier@enwikiquote Cspurrier@enwikiversity Cspurrier@fawiki Cspurrier@itwiki Cspurrier@metawiki Cspurrier@ptwiki Cspurrier@simplewiki Cspurrier@ukwiki Cspurrier@zhwiki Céréales Killer@frwiki DBarratt (WMF)@testwiki DGG@enwiki DHN@viwiki DJackson (WMF)@testwiki DMaza (WMF)@testwiki DR@ruwiki DWalden (WMF)@frwiki DWalden (WMF)@testwiki Daedalus@svwiki Daimore@ptwiki Damzow@hewiki Dan Koehl@specieswiki Daniel Quinlan@enwiki Daniel@enwiki Daniuu@arwiki Daniuu@loginwiki Daniuu@metawiki Daniuu@nlwiki Daniuu@testwiki Danmichaelo@nowiki DannyH (WMF)@mediawikiwiki DannyH (WMF)@testwiki DannyS712@testwiki Danu Widjajanto@idwiki Darkoneko@aswiki Darkoneko@brwiki Darkoneko@bugwiki Darkoneko@cswiki Darkoneko@cswikisource Darkoneko@cswiktionary Darkoneko@dawiki Darkoneko@enwiki Darkoneko@enwikiversity Darkoneko@eswiki Darkoneko@fawiki Darkoneko@frwiki Darkoneko@frwikiquote Darkoneko@glwiki Darkoneko@huwiki Darkoneko@jawiktionary Darkoneko@mediawikiwiki Darkoneko@metawiki Darkoneko@mlwiki Darkoneko@newiki Darkoneko@nlwikiquote Darkoneko@ruwiktionary Darkoneko@skwiki Darkoneko@skwikisource Darkoneko@sourceswiki Darkoneko@srwiki Darkoneko@stwiki Darkoneko@testwiki Darkoneko@trwiki Darkoneko@ukwiki Darkoneko@viwiki Darkoneko@warwiki Darkoneko@wawiki Darkoneko@wowiki Darkoneko@yiwiki DatGuy@enwiki Datrio@fawiki David Fuchs@enwiki David Gerard@enwiki David Wadie Fisher-Freberg@idwiki Dbeef@enwiki Dbl2010@azwiki Dbl2010@kowiktionary Dbl2010@testwiki Dbl2010@trwiki Dbl2010@vlswiki Dcastor@svwiki Deineka@ukwiki Demicx@bswiki Der-Wir-Ing@dewiki DerHexer@loginwiki DerHexer@metawiki Derbeth@enwikibooks Deror avi@hewiki Deskana (WMF)@enwiki Deskana (WMF)@mediawikiwiki Deskana@enwiki Dferg@acewiki Dferg@bat_smgwiki Dferg@bewiki Dferg@bgwiki Dferg@brwiki Dferg@cebwiki Dferg@enwikiversity Dferg@eowiki Dferg@extwiki Dferg@fawiki Dferg@frwikiversity Dferg@gdwiki Dferg@glwiktionary Dferg@hawwiki Dferg@hiwiki Dferg@hywiki Dferg@iswiki Dferg@itwikiquote Dferg@jawikiquote Dferg@kawiki Dferg@kowiki Dferg@kshwiki Dferg@lawiki Dferg@lvwiki Dferg@mkwiktionary Dferg@mswiki Dferg@ndswiki Dferg@orwiktionary Dferg@pmswiki Dferg@ptwikibooks Dferg@ptwikisource Dferg@ptwiktionary Dferg@quwiki Dferg@rowiki Dferg@siwiki Dferg@specieswiki Dferg@sqwiki Dferg@swwiki Dferg@tewiki Dferg@ukwiki Dferg@uzwiki Dferg@viwiki Dferg@vowiki Dferg@wuuwiki Dferg@xalwiki Dferg@xhwiki Dferg@zh_classicalwiki Dferg@zh_yuewiki Dferg@zhwikinews Djordjes@srwiki Djsasso@simplewiki Dmcdevit@wikimania2008wiki Dmitry Gerasimov@ruwiki Dmthoth@kowiki DoRD@enwiki Dominic@enwiki Dominic@enwiktionary Doug Weller@enwiki Doğu@commonswiki Doğu@itwiki Dr-Taher@arwiki Dragoniez@jawiki Drahreg01@dewiki Drbug@ruwiki Dreamy Jazz@enwiki Dreamy Jazz@testwiki Drini@aawiki Drini@afwiki Drini@anwiki Drini@astwiki Drini@azwiki Drini@bgwiki Drini@brwiki Drini@cywiki Drini@dawikibooks Drini@dsbwiki Drini@enwikibooks Drini@enwikiquote Drini@enwikisource Drini@enwikiversity Drini@eowiki Drini@eswiki Drini@etwiki Drini@euwiktionary Drini@extwiki Drini@fawiki Drini@fiwiki Drini@fiwikibooks Drini@fiwikinews Drini@fiwikisource Drini@frpwiki Drini@frwikiquote Drini@gawiki Drini@glwiki Drini@hakwiki Drini@hiwiki Drini@idwiki Drini@iewiki Drini@igwiki Drini@incubatorwiki Drini@iswiki Drini@iswikibooks Drini@itwikisource Drini@itwikisource Drini@jvwiki Drini@kmwiki Drini@kowikiquote Drini@lawiki Drini@lbewiki Drini@lgwiki Drini@lmowiki Drini@lowiki Drini@lvwiki Drini@mediawikiwiki Drini@mlwiki Drini@mswiki Drini@ngwiki Drini@nlwikiquote Drini@nowiki Drini@nowikisource Drini@nvwiki Drini@piwiki Drini@rnwiki Drini@rowiki Drini@ruwiki Drini@ruwikinews Drini@ruwikiquote Drini@scnwiktionary Drini@sgwiki Drini@shwiki Drini@skwiki Drini@snwiki Drini@sowiki Drini@sqwiki Drini@srwiki Drini@swwiki Drini@testwiki Drini@tlwiki Drini@towiki Drini@ukwiki Drini@ukwikinews Drini@ukwiktionary Drini@viwiki Drini@wuuwiki Drini@xalwiki Drini@xhwiki Drini@yiwiki Drini@zh_yuewiki Drini@zhwiki Drmies@enwiki Dtom@hrwiki Dungodung@metawiki Dungodung@srwiki Dungodung@testwiki Durifon@frwiki Dyolf77@arwiki Dyolf77@frwiki Dzordzm@srwiki E.coli@hrwiki EMill-WMF@enwiki EPIC@enwiki EPIC@loginwiki EPIC@metawiki EVula@enwikiquote EdJohnston@enwiki Edmenb@eswiki Effeietsanders@dewiktionary Effeietsanders@enwikiquote Effeietsanders@enwikiversity Effeietsanders@fawiki Effeietsanders@kowiki Effeietsanders@kshwiki Effeietsanders@kuwiki Effeietsanders@nowiki Effeietsanders@ptwiki Effeietsanders@testwiki Einsbor@enwiki Einsbor@loginwiki Ejs-80@fiwiki Elcobbola@commonswiki Eldarion@trwiki Elen of the Roads@enwiki Elfix@cywiktionary Elfix@dewikibooks Elfix@frwiki Elfix@idwikiquote Elfix@itwikinews Elfix@loginwiki Elfix@mywiki Elfix@ptwikibooks Elfix@ptwikiquote Elfix@ptwikisource Elian@dewiki Elli@enwiki Ellywa@nlwiki Elmacenderesi@trwiki Elockid@enwiki Elph@arwiki Elton@astwiki Elton@cebwiki Elton@chwiki Elton@incubatorwiki Elton@jawikinews Elton@loginwiki Elton@mediawikiwiki Elton@plwikiquote Elton@ptwikiquote Elton@shwiki Elton@testwiki Elton@wikidatawiki Elton@zhwiktionary Elwood@itwiki Emir Kotromanić@bswiki Emufarmers@enwiki Emufarmers@eswiki Emufarmers@metawiki Emx@bswiki Enterprisey@enwiki Epinheiro@ptwiki Eptalon@simplewiki Erkan Yilmaz@enwikiversity Erwin85@zhwiki Erwin@nlwiki Essjay@enwiki Eta Carinae@ptwiki Euryalus@enwiki Eusebius@commonswiki EvgenyGenkin@ruwiki Ex13@hrwiki FT2@enwiki Faendalimas@specieswiki Faso@jawiki FayssalF@enwiki Felipe da Fonseca@ptwiki Ferret@enwiki Filzstift@dewiki Firefly@enwiki Fitindia@commonswiki FloNight@enwiki Fluff@svwiki FoBe@huwiki Fr33kman@simplewiki Frank@enwiki Fred Bauder@enwiki Freetrashbox@jawiki Fritzpoll@enwiki Gac@itwiki Galahad@eswiki Galahad@fawiki Galahad@trwiki Gamaliel@enwiki GeneralNotability@enwiki Geonuch@thwiki Giraffer@enwiki Girth Summit@enwiki Gmaxwell@commonswiki Gnom@dewiki Goo3@ukwiki GorillaWarfare@enwiki Gpvos@nlwiki Gribeco@frwiki Grin@huwiki Groucho NL@nlwiki Guerillero@enwiki Guillom@arwiki Guillom@cvwiki Guillom@dawiki Guillom@fawiki Guillom@fiwikiquote Guillom@foundationwiki Guillom@frwiki Guillom@frwikinews Guillom@frwikisource Guillom@frwiktionary Guillom@iawiki Guillom@ilowiki Guillom@iowiki Guillom@iswiki Guillom@itwikinews Guillom@kabwiki Guillom@kowiki Guillom@rowiki Guillom@siwiktionary Guillom@trwiki Gutza@rowiki Gvf@itwiki Góngora@cawiki HJ Mitchell@enwiki HVL@ptwiki Ha98574@kowiki HaeB@dewiki HaithamS (WMF)@enwiki HakanIST@loginwiki Hardscarf@nlwiki Harel@hewiki Haros@nowiki Harriv@fiwiki Hasley@barwiki Hasley@loginwiki Hasley@metawiki Hayabusa future@idwiki Hei ber@dewiki Hei ber@enwiki Hephaion@dewiki Herbythyme@commonswiki Herbythyme@enwikibooks Herbythyme@metawiki Herbythyme@nowiki Hersfold@enwiki Hexasoft@frwiki Hidayatsrf@idwiki Hidro@hewiki Hispa@eswiki Hjvannes@nlwiki Hoo man@loginwiki HouseBlaster@enwiki Huji@fawiki Hunyadym@huwiki Hyméros@frwiki INeverCry@commonswiki IRTC1015@kowiki Ilya Voyager@ruwiki IlyaHaykinson@enwikinews Indech@ptwiki Infinite0694@eswiki Infinite0694@jawiki Infinite0694@loginwiki Infinite0694@metawiki Iridescent@enwiki IvanLanin@idwiki Ivanvector@enwiki Izno@enwiki J.delanoy@enwiki JAbrams (WMF)@cebwiki JAbrams (WMF)@commonswiki JAbrams (WMF)@cswiktionary JAbrams (WMF)@enwiki JAbrams (WMF)@eowiki JAbrams (WMF)@frwiki JAbrams (WMF)@jawiki JAbrams (WMF)@metawiki JAbrams (WMF)@ptwiki JAbrams (WMF)@wikidatawiki JCrespo (WMF)@labswiki JEissfeldt (WMF)@dewiki JEissfeldt (WMF)@enwiki JEissfeldt (WMF)@jawiki JJMC89@eswiki JJMC89@loginwiki JSutherland (WMF)@afwiki JSutherland (WMF)@arwiki JSutherland (WMF)@arwikisource JSutherland (WMF)@azwiki JSutherland (WMF)@bgwiki JSutherland (WMF)@commonswiki JSutherland (WMF)@cswiki JSutherland (WMF)@dawiki JSutherland (WMF)@dewiki JSutherland (WMF)@enwiki JSutherland (WMF)@enwikibooks JSutherland (WMF)@enwikiquote JSutherland (WMF)@enwiktionary JSutherland (WMF)@eswiki JSutherland (WMF)@fawiki JSutherland (WMF)@frwiki JSutherland (WMF)@frwikiquote JSutherland (WMF)@frwikiversity JSutherland (WMF)@hewiki JSutherland (WMF)@hrwiki JSutherland (WMF)@huwiki JSutherland (WMF)@hywiki JSutherland (WMF)@idwiki JSutherland (WMF)@itwiki JSutherland (WMF)@jawiki JSutherland (WMF)@kowiki JSutherland (WMF)@loginwiki JSutherland (WMF)@mediawikiwiki JSutherland (WMF)@metawiki JSutherland (WMF)@nlwiki JSutherland (WMF)@nowiki JSutherland (WMF)@plwiki JSutherland (WMF)@ptwiki JSutherland (WMF)@ruwiki JSutherland (WMF)@simplewiki JSutherland (WMF)@svwiki JSutherland (WMF)@testwiki JSutherland (WMF)@ukwiki JSutherland (WMF)@wikidatawiki JSutherland (WMF)@zhwiki JWSchmidt@enwikiversity JWang (WMF)@testwiki Jafeluv@fiwiki Jagro@cswiki Jake Park@eswiki Jalexander-WMF@arwiki Jalexander-WMF@azwiki Jalexander-WMF@bgwiki Jalexander-WMF@cawiki Jalexander-WMF@commonswiki Jalexander-WMF@cswiki Jalexander-WMF@dawiki Jalexander-WMF@dewiki Jalexander-WMF@enwiki Jalexander-WMF@enwikibooks Jalexander-WMF@enwikiquote Jalexander-WMF@enwikisource Jalexander-WMF@enwikiversity Jalexander-WMF@enwikivoyage Jalexander-WMF@enwiktionary Jalexander-WMF@eswiki Jalexander-WMF@eswikivoyage Jalexander-WMF@frwiki Jalexander-WMF@gawiki Jalexander-WMF@hewiki Jalexander-WMF@hewikisource Jalexander-WMF@incubatorwiki Jalexander-WMF@iswiki Jalexander-WMF@itwiki Jalexander-WMF@kowiki Jalexander-WMF@kowikibooks Jalexander-WMF@kowikisource Jalexander-WMF@loginwiki Jalexander-WMF@mediawikiwiki Jalexander-WMF@metawiki Jalexander-WMF@mywiki Jalexander-WMF@nowiki Jalexander-WMF@nowiktionary Jalexander-WMF@plwiki Jalexander-WMF@ptwiki Jalexander-WMF@ruwiki Jalexander-WMF@ruwikibooks Jalexander-WMF@ruwikisource Jalexander-WMF@simplewiki Jalexander-WMF@specieswiki Jalexander-WMF@svwiki Jalexander-WMF@testwiki Jalexander-WMF@wikidatawiki Jalexander-WMF@zhwiki Jalexander@foundationwiki Jalexander@labswiki James LL@loginwiki Jameslwoodward@commonswiki Jamesofur@simplewiki Jamie Tubers@enwiki Janorovic Volkov@idwiki Japiot@nlwiki Jasper Deng@wikidatawiki Jayjg@enwiki Jbribeiro1@ptwiki Jcb@nlwiki Jclemens@enwiki Jdforrester (WMF)@enwiki Jdforrester (WMF)@enwikiquote Jdforrester@enwiki Jean-Christophe BENOIST@frwiki Jeffq@enwikiquote Jimbo Wales@enwiki Jimmy Xu@zhwiki Jmrebes@cawiki Jniemenmaa@fiwiki Joe Roe@enwiki Johannnes89@loginwiki Johannnes89@metawiki John Vandenberg@enwiki John Vandenberg@enwikisource Jon Harald Søby@akwiki Jon Harald Søby@alswiki Jon Harald Søby@cawiki Jon Harald Søby@cswiki Jon Harald Søby@dewiki Jon Harald Søby@enwiki Jon Harald Søby@enwikiquote Jon Harald Søby@fawiki Jon Harald Søby@fiwikibooks Jon Harald Søby@fiwikisource Jon Harald Søby@frwiki Jon Harald Søby@iswiki Jon Harald Søby@itwiki Jon Harald Søby@kowiki Jon Harald Søby@metawiki Jon Harald Søby@nnwiki Jon Harald Søby@nowiki Jon Harald Søby@nowikibooks Jon Harald Søby@plwikibooks Jon Harald Søby@plwikinews Jon Harald Søby@plwiktionary Jon Harald Søby@ruwiki Jon Harald Søby@skwiki Jon Harald Søby@svwiki Jon Harald Søby@svwikinews Jon Harald Søby@testwiki Jon Harald Søby@trwiki Jon Harald Søby@yiwiki Jon Kolbert@loginwiki Joutbis@cawiki Jpgordon@enwiki Jrogers (WMF)@metawiki Julle@svwiki Julle@testwiki Just Sayori@thwiki JusteJuju10@frwiki Jyothis@eswiktionary Jyothis@loginwiki Jyothis@metawiki KColeman-WMF@testwiki KHarlan (WMF)@loginwiki KHarlan (WMF)@testwiki KLevan (WMF)@arwiki KLevan (WMF)@commonswiki KLevan (WMF)@enwiki KLevan (WMF)@hewiki KLevan (WMF)@idwiki KLevan (WMF)@metawiki KMT@jawiki KRLS@cawiki Kaare@dawiki Kal-El~bswiki@bswiki Kalliope (WMF)@azwiki Kalliope (WMF)@commonswiki Kalliope (WMF)@dewiki Kalliope (WMF)@enwiki Kalliope (WMF)@enwikiversity Kalliope (WMF)@enwiktionary Kalliope (WMF)@eswiki Kalliope (WMF)@frwiki Kalliope (WMF)@frwiktionary Kalliope (WMF)@idwiki Kalliope (WMF)@itwiki Kalliope (WMF)@jawiki Kalliope (WMF)@loginwiki Kalliope (WMF)@metawiki Kalliope (WMF)@outreachwiki Kalliope (WMF)@testwiki Kalliope (WMF)@zhwiki Kanjy@jawiki Karol007@plwiki Karsten11@dewiki Kbrown (WMF)@arwiki Kbrown (WMF)@commonswiki Kbrown (WMF)@dewiki Kbrown (WMF)@enwiki Kbrown (WMF)@enwikibooks Kbrown (WMF)@enwikiversity Kbrown (WMF)@enwikivoyage Kbrown (WMF)@eowiki Kbrown (WMF)@eswiki Kbrown (WMF)@eswikibooks Kbrown (WMF)@frwiki Kbrown (WMF)@frwiktionary Kbrown (WMF)@itwiki Kbrown (WMF)@loginwiki Kbrown (WMF)@metawiki Kbrown (WMF)@ptwiki Kbrown (WMF)@simplewiki Kbrown (WMF)@testwiki Kbrown (WMF)@trwiki Kbrown (WMF)@zhwiki Keegan@enwiki Kegns@zhwiki Kelapstick@enwiki Kiran Gopi@mlwiki Kirill Lokshin@enwiki KnightLago@enwiki KnudW@dawiki Koavf@specieswiki KonstantinaG07@loginwiki KovacsUr@huwiki KrakatoaKatie@enwiki Krd@commonswiki Krd@loginwiki Krd@metawiki Ks aka 98@jawiki Ks0stm@enwiki Kulac@dewiki Kv75@ruwiki Kylu@enwiki Kylu@metawiki L235@enwiki LD@frwiki LFaraone@enwiki Laaknor@metawiki Laaknor@nowiki Ladsgroup@fawiki Ladsgroup@labswiki Ladsgroup@testwiki Ladypine@hewiki Lankiveil@enwiki Lanwi1@zhwiki Lar@alswiki Lar@anwiki Lar@arwiki Lar@bgwiki Lar@commonswiki Lar@dewiki Lar@enwiki Lar@enwikiquote Lar@enwikisource Lar@enwikiversity Lar@fawiki Lar@frwiki Lar@hywiki Lar@kywiktionary Lar@metawiki Lar@mswiki Lar@nlwiki Lar@nlwikiquote Lar@plwiki Lar@rowiki Lar@scowiki Lar@simplewiki Lar@simplewiktionary Lar@thwiki Lar@tiwiki Lar@trwiki Lar@ukwiki Lar@viwiki Lar@zhwiki Le chat perché@frwiki Lechatjaune@ptwiki Leinad pl@rowiki Leinad@metawiki Leinad@plwiki Lewisiscrazy@frwiki Lijealso@ptwiki Linedwell@frwiki Linedwell@loginwiki Liso@skwiki Little Sunshine@ptwiki Lofty abyss@abwiki Lofty abyss@acewiki Lofty abyss@astwiki Lofty abyss@bclwiki Lofty abyss@betawikiversity Lofty abyss@bnwiki Lofty abyss@cawiktionary Lofty abyss@chrwiki Lofty abyss@crwiki Lofty abyss@cswikibooks Lofty abyss@cswiktionary Lofty abyss@dawikisource Lofty abyss@dawiktionary Lofty abyss@dewikibooks Lofty abyss@dewikinews Lofty abyss@dewiktionary Lofty abyss@eewiki Lofty abyss@enwikibooks Lofty abyss@enwikiversity Lofty abyss@enwikivoyage Lofty abyss@etwiki Lofty abyss@fawiki Lofty abyss@glwiki Lofty abyss@huwiktionary Lofty abyss@idwikibooks Lofty abyss@idwikiquote Lofty abyss@idwikisource Lofty abyss@idwiktionary Lofty abyss@iewiki Lofty abyss@ilowiki Lofty abyss@incubatorwiki Lofty abyss@itwikisource Lofty abyss@itwikisource Lofty abyss@jvwiki Lofty abyss@kmwiki Lofty abyss@kowikiquote Lofty abyss@lawiki Lofty abyss@lbewiki Lofty abyss@lgwiki Lofty abyss@lmowiki Lofty abyss@lowiki Lofty abyss@lvwiki Lofty abyss@mediawikiwiki Lofty abyss@metawiki Lofty abyss@mnwiki Lofty abyss@mswiki Lofty abyss@ndswiki Lofty abyss@nlwikibooks Lofty abyss@nlwikiquote Lofty abyss@nlwiktionary Lofty abyss@nowiki Lofty abyss@nycwikimedia Lofty abyss@nywiki Lofty abyss@omwiki Lofty abyss@outreachwiki Lofty abyss@papwiki Lofty abyss@pflwiki Lofty abyss@pihwiki Lofty abyss@plwikiquote Lofty abyss@plwikisource Lofty abyss@ptwikibooks Lofty abyss@ptwikinews Lofty abyss@ptwikiquote Lofty abyss@ptwikiversity Lofty abyss@ptwikivoyage Lofty abyss@ptwiktionary Lofty abyss@quwiki Lofty abyss@simplewiki Lofty abyss@simplewiktionary Lofty abyss@sourceswiki Lofty abyss@specieswiki Lofty abyss@suwiki Lofty abyss@svwikinews Lofty abyss@svwikiquote Lofty abyss@swwiki Lofty abyss@tiwiktionary Lofty abyss@trwikisource Lofty abyss@vewiki Lofty abyss@warwiki Lofty abyss@wikidatawiki Lofty abyss@wuuwiki Lofty abyss@yiwiki Lofty abyss@yowiki Lofty abyss@zh_classicalwiki Lord Mota@ptwiki Luk@enwiki Luna Santin@enwiki Lusitana@ptwiki Lusum@itwiki Lymantria@commonswiki Lymantria@wikidatawiki Lzur@plwiki M7@kawiki M7@loginwiki M7@metawiki M7@simplewiki MAna (WMF)@testwiki MBisanz@enwiki MBq@dewiki MF-Warburg@loginwiki MFischer (WMF)@enwiki MGA73@dawiki MGodwin@enwiki MPelletier (WMF)@enwiki MPelletier (WMF)@wikidatawiki MPinchuk (WMF) (usurped)@mediawikiwiki MPostoronca-WMF@testwiki MSzabo-WMF@testwiki MZaplotnik@slwiki Mackensen@enwiki Madaki@itwiki Magister Mathematicae@akwiki Magister Mathematicae@alswiki Magister Mathematicae@angwiki Magister Mathematicae@arwiki Magister Mathematicae@aswiki Magister Mathematicae@azwiktionary Magister Mathematicae@barwiki Magister Mathematicae@bawiki Magister Mathematicae@bpywiki Magister Mathematicae@bugwiki Magister Mathematicae@cawiki Magister Mathematicae@chwiki Magister Mathematicae@commonswiki Magister Mathematicae@cowiki Magister Mathematicae@cswiki Magister Mathematicae@enwiki Magister Mathematicae@enwikinews Magister Mathematicae@eswiki Magister Mathematicae@eswikibooks Magister Mathematicae@eswikinews Magister Mathematicae@eswikiquote Magister Mathematicae@eswikisource Magister Mathematicae@eswikiversity Magister Mathematicae@fawiki Magister Mathematicae@ffwiki Magister Mathematicae@fiwikiquote Magister Mathematicae@fjwiki Magister Mathematicae@gawiktionary Magister Mathematicae@gdwiki Magister Mathematicae@itwikibooks Magister Mathematicae@kabwiki Magister Mathematicae@kowiki Magister Mathematicae@kwwiktionary Magister Mathematicae@metawiki Magister Mathematicae@nahwiki Magister Mathematicae@pihwiki Magister Mathematicae@ptwiki Magister Mathematicae@quwiki Magister Mathematicae@simplewiki Magister Mathematicae@simplewiktionary Magister Mathematicae@specieswiki Magister Mathematicae@stwiki Magister Mathematicae@tswiki Magister Mathematicae@ugwiki Magister Mathematicae@uzwiki Magister Mathematicae@zawiki MagnusA@svwiki Magog the Ogre@commonswiki Mailer diablo@enwiki Majorly@simplewiki Malatinszky@huwiki Marc Mongenet@frwiki MarcGarver@astwiki MarcGarver@azwikibooks MarcGarver@bxrwiki MarcGarver@cewiki MarcGarver@chrwiktionary MarcGarver@crwiki MarcGarver@cswikibooks MarcGarver@cswikiquote MarcGarver@enwikibooks MarcGarver@enwikiquote MarcGarver@enwikiversity MarcGarver@enwikivoyage MarcGarver@eswikiquote MarcGarver@fawiki MarcGarver@hawiki MarcGarver@hewikivoyage MarcGarver@hiwiki MarcGarver@idwikiquote MarcGarver@idwikisource MarcGarver@incubatorwiki MarcGarver@kabwiki MarcGarver@loginwiki MarcGarver@ltwiktionary MarcGarver@mediawikiwiki MarcGarver@mswiki MarcGarver@nlwikimedia MarcGarver@nlwikivoyage MarcGarver@nowiki MarcGarver@piwiki MarcGarver@ptwikivoyage MarcGarver@scowiki MarcGarver@slwiki MarcGarver@svwikivoyage MarcGarver@wuuwiki MarcGarver@zhwiki Marcelo Victor@ptwiki MarcoAurelio@astwiki MarcoAurelio@barwiki MarcoAurelio@chrwiki MarcoAurelio@eewiki MarcoAurelio@enwiki MarcoAurelio@eswikiquote MarcoAurelio@euwiki MarcoAurelio@glwiki MarcoAurelio@loginwiki MarcoAurelio@mediawikiwiki MarcoAurelio@metawiki MarcoAurelio@nowiki MarcoAurelio@ptwiki MarcoAurelio@ptwikiquote MarcoAurelio@sourceswiki MarcoAurelio@testwiki MarcoAurelio@zhwiki Mardetanha@commonswiki Mardetanha@enwiki Mardetanha@loginwiki Mardetanha@metawiki Marine-Blue@jawiki Mark Bergsma@enwiki Mark Bergsma@nlwiki Markov@frwiki Martin H.@commonswiki Martin Urbanec (WMF)@cswiki Martin Urbanec (WMF)@cswikiversity Martin Urbanec (WMF)@loginwiki Martin Urbanec (WMF)@metawiki Martin Urbanec (WMF)@testwiki Martin Urbanec@cswiki Martin Urbanec@enwiki Martin Urbanec@loginwiki Martin Urbanec@metawiki Martin Urbanec@testwiki Masti@loginwiki Masti@plwiki Matanya@enwiki Matanya@hewiki Matanya@testwiki Materialscientist@enwiki Mathis B@frwiki Mathonius@loginwiki Matiia@loginwiki MatthiasGor@plwiki Maurilbert@frwiki MaxSem@bgwiki MaxSem@elwiki MaxSem@enwiki MaxSem@enwikiquote MaxSem@enwikisource MaxSem@fowiktionary MaxSem@hrwiki MaxSem@metawiki MaxSem@nowiki MaxSem@nowikibooks MaxSem@plwiki MaxSem@shwiktionary MaxSem@simplewiki MaxSem@skwiki MaxSem@srwiki MaxSem@stwiki MaxSem@svwiki MaxSem@ukwiki MaxSem@yiwiki MaxSem@zhwiki Maxim@enwiki Mdennis (WMF)@commonswiki Mdennis (WMF)@dewiki Mdennis (WMF)@enwiki Mdennis (WMF)@enwikinews Mdennis (WMF)@enwikiquote Mdennis (WMF)@enwikisource Mdennis (WMF)@frwiki Mdennis (WMF)@frwiktionary Mdennis (WMF)@loginwiki Mdennis (WMF)@metawiki Mdennis (WMF)@simplewiki Mdennis (WMF)@svwiki MdsShakil@loginwiki MdsShakil@test2wiki MdsShakil@testwiki Melos@itwiki Melos@loginwiki Melos@sswiki Meno25@arwiki Mentisock@wikidatawiki Mercy@metawiki Meursault2004@idwiki Mido@arwiki Midom@enwiki Midom@metawiki Mike V@enwiki Mike.lifeguard@commonswiki Mike.lifeguard@enwikibooks Mike.lifeguard@metawiki Mike.lifeguard@pmswiki Mike.lifeguard@rmywiki Mike.lifeguard@tywiki MikkoM@fiwiki Millennium bug@ptwiki Millosh@azwiki Millosh@bswiki Millosh@cswiki Millosh@dewiktionary Millosh@enwiki Millosh@fawiki Millosh@gdwiktionary Millosh@hrwiki Millosh@kowiki Millosh@metawiki Millosh@pmswiki Millosh@ruwiki Millosh@ruwikisource Millosh@sqwiki Millosh@trwiki Millosh@zhwiki Minderbinder@dewiki Minorax@metawiki Mkdw@enwiki Mmenal@frwiki Moneytrees@enwiki Montgomery@eswiki Mormegil@cswiki Morven@enwiki MrJaroslavik@loginwiki MrJaroslavik@testwiki Msz2001@plwiki Mtarch11@itwiki Mtarch11@metawiki MuZemike@enwiki MusikAnimal@enwiki MusikAnimal@loginwiki Mwpnl@trwiki Mxn@viwiki Mykola7@loginwiki Mykola7@ukwiki Mys 721tx@zhwiki Mz7@enwiki NForrester (WMF)@bewiki NForrester (WMF)@bgwiki NForrester (WMF)@commonswiki NForrester (WMF)@enwiki NForrester (WMF)@eswiki NForrester (WMF)@metawiki NForrester (WMF)@ruwiki NForrester (WMF)@shwiki NForrester (WMF)@srwiki NForrester (WMF)@wikidatawiki NForrester (WMF)@zhwiki NKohli (WMF)@testwiki NNair (WMF)@enwiki NahidSultan (WMF)@arwiki NahidSultan (WMF)@arwikinews NahidSultan (WMF)@bnwiki NahidSultan (WMF)@bnwiktionary NahidSultan (WMF)@cawiki NahidSultan (WMF)@commonswiki NahidSultan (WMF)@enwiki NahidSultan (WMF)@enwikinews NahidSultan (WMF)@enwikiquote NahidSultan (WMF)@eswiki NahidSultan (WMF)@ffwiki NahidSultan (WMF)@foundationwiki NahidSultan (WMF)@frwiki NahidSultan (WMF)@hewiki NahidSultan (WMF)@jawiki NahidSultan (WMF)@jawiktionary NahidSultan (WMF)@knwiki NahidSultan (WMF)@kowiki NahidSultan (WMF)@loginwiki NahidSultan (WMF)@mediawikiwiki NahidSultan (WMF)@metawiki NahidSultan (WMF)@miwiki NahidSultan (WMF)@mkwiki NahidSultan (WMF)@mrwiki NahidSultan (WMF)@mswiki NahidSultan (WMF)@ptwiki NahidSultan (WMF)@rowiki NahidSultan (WMF)@ruwiki NahidSultan (WMF)@simplewiki NahidSultan (WMF)@specieswiki NahidSultan (WMF)@srwiki NahidSultan (WMF)@tawiki NahidSultan (WMF)@ukwiki NahidSultan (WMF)@uzwiki NahidSultan (WMF)@wikidatawiki NahidSultan (WMF)@zhwiki NahidSultan@bnwiki NahidSultan@loginwiki Nakor@frwiki Nataev@uzwiki NativeForeigner@enwiki Natuur12@nlwiki Nedops@plwiki Nelson Teixeira@ptwiki Nettadi@hewiki Neukoln@hewiki Newyorkbrad@enwiki Nick1915@bat_smgwiki Nick1915@elwiki Nick1915@fawiki Nick1915@itwikibooks Nick1915@itwiktionary Nick1915@kowiki Nick1915@kwwiki Nick1915@lmowiki Nick1915@metawiki Nick1915@ptwiki Nick1915@scnwiki Nick1915@trwiki Nick1915@zhwiki NickK@ukwiki NinjaRobotPirate@enwiki Nishkid64@enwiki NoFWDaddress@frwiki Nohirara@idwiki Noumenon@trwiki NuclearWarfare@enwiki Nuno Tavares@ptwiki Nux@plwiki Nyenyec@huwiki OJJ@cswiki Octahedron80@thwiki OhanaUnited@enwikivoyage OneLittleMouse@ruwiki OnlyJonny@ptwiki Opabinia regalis@enwiki Operator873@loginwiki Operator873@simplewiki Ori@hewiki Oscar@commonswiki Oscar@dewiki Oscar@enwiki Oscar@frwiki Oscar@metawiki Oscar@nlwiki Oscar@nlwikibooks Oscarami@eswiki Oshwah@enwiki PEarley (WMF)@enwiki PSaxena (WMF)@mediawikiwiki PSaxena (WMF)@testwiki Paginazero@commonswiki Paginazero@enwiki Paginazero@enwikiquote Paginazero@itwiki Paginazero@itwikibooks Paginazero@ruwiki Paginazero@yiwiki Pallerti@huwiki Palnatoke@dawiki Pap3rinik@itwiki Pathoschild@commonswiki Pathoschild@cywiki Pathoschild@elwiki Pathoschild@enwiki Pathoschild@enwikinews Pathoschild@enwikiquote Pathoschild@enwikisource Pathoschild@eowiki Pathoschild@eswiki Pathoschild@eswikiquote Pathoschild@etwiki Pathoschild@fawiki Pathoschild@fiwikinews Pathoschild@hewiki Pathoschild@hsbwiki Pathoschild@huwiki Pathoschild@idwiki Pathoschild@incubatorwiki Pathoschild@itwiki Pathoschild@itwikiquote Pathoschild@klwiki Pathoschild@kwwiktionary Pathoschild@lbwiki Pathoschild@metawiki Pathoschild@miwiki Pathoschild@mlwiki Pathoschild@nlwiki Pathoschild@nnwiki Pathoschild@ocwiki Pathoschild@ptwiki Pathoschild@ruwiktionary Pathoschild@simplewiki Pathoschild@siwiki Pathoschild@sqwiki Pathoschild@srwiki Pathoschild@urwiktionary Pathoschild@vewiki Pathoschild@yiwiki Pathoschild@zhwiki Pathoschild@zhwiktionary PatríciaR@ptwiki Penn Station@jawiki Perrak@dewiki Pete Forsyth (WMF)@enwiki PeterSymonds@metawiki Peterdownunder@simplewiki PhilKnight@enwiki Philippe (WMF)@azwiki Philippe (WMF)@commonswiki Philippe (WMF)@dewiki Philippe (WMF)@enwiki Philippe (WMF)@enwikibooks Philippe (WMF)@enwikinews Philippe (WMF)@enwikiquote Philippe (WMF)@enwikisource Philippe (WMF)@enwikiversity Philippe (WMF)@enwikivoyage Philippe (WMF)@eswiki Philippe (WMF)@fawiki Philippe (WMF)@frwiki Philippe (WMF)@frwiktionary Philippe (WMF)@itwiki Philippe (WMF)@loginwiki Philippe (WMF)@mediawikiwiki Philippe (WMF)@metawiki Philippe (WMF)@mgwiktionary Philippe (WMF)@nlwiki Philippe (WMF)@simplewiki Philippe (WMF)@wikidatawiki Philippe (WMF)@xhwiki Philippe (WMF)@zhwiki Philippe@enwiki Piku@frwiki Pmlineditor@loginwiki Polimerek@plwiki Polimerek@plwikimedia Ponyo@enwiki Postrach@cswiki Poudou!@frwiki Pouyana@fawiki Premeditated Chaos@enwiki Primefac@enwiki Prométhée@frwiki Ptjackyll@plwiki Pundit@plwiki PurpleBuffalo@hewiki Putnik@ruwiki Q-bit array@ruwiki Queix@frwiki RLuts@ukwiki RadiX@bhwiki RadiX@cswikisource RadiX@enwikiversity RadiX@hiwiki RadiX@loginwiki RadiX@pswiki RadiX@ptwiki RadiX@ptwikibooks RadiX@ptwikiquote RadiX@svwikiversity Rastrojo@eswiki Raul654@enwiki Rax@dewiki Razimantv@mlwiki Rdsmith4@arwiki Rdsmith4@biwiki Rdsmith4@etwiki Rdsmith4@fawiki Rdsmith4@gawiki Rdsmith4@huwiki Rdsmith4@idwiki Rdsmith4@jawiki Rdsmith4@kowiki Rdsmith4@lmowiki Rdsmith4@lvwiki Rdsmith4@metawiki Rdsmith4@newiktionary Rdsmith4@nrmwiki Rdsmith4@rowiki Rdsmith4@rowiktionary Rdsmith4@scowiki Rdsmith4@trwiki Rdsmith4@zhwiki ReAl@ukwiki Reaper Eternal@enwiki Redux@enwiki Reedy (WMF)@arwiki Reedy (WMF)@enwiki Reedy (WMF)@mediawikiwiki Reedy (WMF)@metawiki Reedy (WMF)@testwiki Reedy@commonswiki Reedy@enwiki Reedy@itwiki Reedy@labswiki Reedy@wikidatawiki Rei-artur@ptwiki Renamed user mou89p43twvqcvm8ut9w3@enwiki Revi C.@betawikiversity Revi C.@dewikiquote Revi C.@enwiki Revi C.@enwikinews Revi C.@enwikivoyage Revi C.@eswikinews Revi C.@kowiktionary Revi C.@loginwiki Revi C.@mediawikiwiki Revi C.@rowiki Revi C.@ruwikinews Revi C.@ruwikiquote Revi C.@smnwiki Revi C.@zh_classicalwiki Revi C.@zhwiki Rexcornot@frwiki Reza1615@fawiki RiazACU@bnwiki Richwales@enwiki Richwales@eswiki RickinBaltimore@enwiki Risker@enwiki Rlevse@enwiki RobLa-WMF@enwiki RobLa-WMF@metawiki RobLa-WMF@plwiki Rodasmith@enwiktionary Roger Davies@enwiki Rojelio@itwiki Romihaitza@simplewiki Romihaitza@yiwiki RoySmith@arwiki RoySmith@enwiki RoySmith@metawiki Rsocol@rowiki Rubin16@ruwiki Ruslik0@loginwiki Ruslik0@nlwikibooks Ruthven@itwiki Ruthven@testwiki Ruy Pugliesi@mediawikiwiki Rxy@loginwiki Rxy@wikidatawiki Ryan Kaldari (WMF)@commonswiki Ryan Kaldari (WMF)@enwiki Ryan Lane (WMF)@labswiki SB Johnny@commonswiki SB Johnny@enwikibooks SB Johnny@enwikiversity SBJohnny@enwikibooks SBassett (WMF)@enwiki SBassett (WMF)@mediawikiwiki SBassett (WMF)@metawiki SBassett (WMF)@testwiki SEPRodrigues@ptwiki SHB2000@enwikivoyage SHB2000@loginwiki SMP@cawiki SNg (WMF)@enwiki SNg (WMF)@metawiki SNg (WMF)@nlwiki SPoore (WMF)@enwiki SPoore (WMF)@hrwiki SPoore (WMF)@itwiki SPoore (WMF)@metawiki SPoore (WMF)@simplewiki SPoore (WMF)@testwiki SQL@enwiki SSandu-WMF@enwiki SSpalding (WMF)@enwiki ST47@enwiki ST47@testwiki Sadrettin@trwiki Sahehco@fawiki Sahehco@testwiki Sakretsu@itwiki Sakretsu@loginwiki Salvio giuliano@enwiki Sam Korn@enwiki Samuel (WMF)@enwiki Samuel (WMF)@eswiki Samuel (WMF)@frwiki Samuel (WMF)@iawiki Samuel (WMF)@metawiki Samuel (WMF)@testwiki Samuel (WMF)@ukwiki Saper@plwiki Savh@itwikibooks Savh@loginwiki Sbisolo@itwiki Schiste@frwiki Schlum@frwiki Schniggendiller@loginwiki ScottishFinnishRadish@enwiki Sdrqaz@enwiki SelfieCity@enwikivoyage Seraphimblade@enwiki Shanel@aawiki Shanel@arwiki Shanel@cswiki Shanel@cswikiquote Shanel@eewiki Shanel@elwikibooks Shanel@enwiki Shanel@enwikiquote Shanel@eswikisource Shanel@fawiki Shanel@frwikinews Shanel@frwiktionary Shanel@igwiki Shanel@itwikinews Shanel@jawikinews Shanel@kowiki Shanel@mlwiki Shanel@ndswiki Shanel@ptwiki Shanel@ptwikinews Shanel@ptwiktionary Shanel@simplewiktionary Shanel@skwiki Shanel@skwiktionary Shanel@tiwiki Shanel@ukwiki Shanel@vecwiki Shanel@yiwiki Shanel@zhwiki Shanmugamp7@enwiki Shanmugamp7@loginwiki Shell Kinney@enwiki Shivanarayana@itwiki Shizhao@betawikiversity Shizhao@commonswiki Shizhao@dewiki Shizhao@enwiki Shizhao@frwiki Shizhao@kowiki Shizhao@kowikisource Shizhao@metawiki Shizhao@ptwikiquote Shizhao@ruwiki Shizhao@viwiki Shizhao@zhwiki Siam2019@thwiki SilkTork@enwiki SilverLocust@enwiki Sir Lestaty de Lioncourt@ptwikinews Sir kiss@hewiki Sir48@commonswiki Sir48@dawiki SirFozzie@enwiki Sjoerddebruin@loginwiki Skenmy@enwikinews Slaporte (WMF)@commonswiki Slaporte (WMF)@enwiki Slaporte (WMF)@ptwiki Smooth O@bswiki Snowdog@itwiki SoWhy@enwiki Sotiale@enwiki Sotiale@kowiki Sotiale@loginwiki Sotiale@metawiki Sotiale@wikidatawiki Spacebirdy@anwiki Spacebirdy@cawiki Spacebirdy@metawiki Spacebirdy@testwiki Spangineer@enwikisource SpeedyGonsales@hrwiki Spicy@enwiki Squasher@dewiki Stanglavine@loginwiki Stanglavine@metawiki Stanglavine@ptwiki Stephen Bain@enwiki Steve Smith@enwiki Stigfinnare@svwiki Strainu@rowiki Stryn@fiwiki Stryn@loginwiki Stwalkerster@enwiki Suisui@jawiki Superpes15@enwiki Superpes15@enwikiquote Superpes15@fawiki Superpes15@itwiki Superpes15@itwikiversity Superpes15@loginwiki Superpes15@metawiki Superspritz@itwiki Surjection@enwiktionary Syp@huwiki Szwedzki@plwiki TBolliger (WMF)@testwiki THargrove (WMF)@plwiki Tarawneh@arwiki Taweetham@thwiki Tbone@fiwiki Tegel@enwiki Tegel@eswiktionary Tegel@loginwiki Tegel@metawiki Tegel@svwiki Teles@enwiki Teles@loginwiki Teles@metawiki Teles@ptwiki Tfinc@enwiki Tfinc@metawiki Tgr (WMF)@commonswiki Tgr (WMF)@enwiki Tgr (WMF)@loginwiki Tgr (WMF)@metawiki Tgr (WMF)@testwiki Tgr (WMF)@trwiki Tgr@huwiki Thatcher@enwiki The Epopt@enwiki The Rambling Man@simplewiki The Squirrel Conspiracy@commonswiki TheDaveRoss@enwiktionary TheStriker@hewiki Theghaz@dewiki Theleekycauldron@enwiki Thenub314@enwikibooks TheresNoTime@enwiki TheresNoTime@loginwiki TheresNoTime@simplewiki Thogo@afwiki Thogo@alswiki Thogo@arzwiki Thogo@azwiktionary Thogo@barwiki Thogo@bat_smgwiki Thogo@bawiki Thogo@bgwiki Thogo@biwiki Thogo@brwiki Thogo@diqwiki Thogo@eewiki Thogo@elwiktionary Thogo@emlwiki Thogo@enwikisource Thogo@enwikiversity Thogo@enwiktionary Thogo@eowiki Thogo@eswiktionary Thogo@euwiki Thogo@euwiktionary Thogo@fawiki Thogo@fowiktionary Thogo@frwikinews Thogo@frwikiquote Thogo@fywiki Thogo@gawiki Thogo@gdwiktionary Thogo@gvwiktionary Thogo@hiwiki Thogo@hrwiki Thogo@huwiki Thogo@hywiki Thogo@itwikiquote Thogo@itwiktionary Thogo@iuwiki Thogo@jawikinews Thogo@jawikisource Thogo@jawiktionary Thogo@kmwiki Thogo@kowiki Thogo@liwiki Thogo@mediawikiwiki Thogo@miwiki Thogo@ndswiki Thogo@nnwiki Thogo@nowiki Thogo@pamwiki Thogo@ptwikinews Thogo@quwiki Thogo@rowiki Thogo@simplewiktionary Thogo@siwiki Thogo@sourceswiki Thogo@sowiki Thogo@specieswiki Thogo@stqwiki Thogo@vecwiki Thogo@viwiki Thogo@vowiki Thogo@wawiki Thogo@xhwiki Thogo@yiwiki Thogo@zhwiki Thryduulf@enwiki Tim Starling (WMF)@dewiki Tim Starling (WMF)@enwiki Tim Starling (WMF)@mediawikiwiki Tim Starling (WMF)@ruwiki Tim Starling@enwiki Tim Starling@labswiki Tim Starling@metawiki Tim Starling@testwiki Timekeepertmk@thwiki Timotheus Canens@enwiki Tinz@dewiki Tiptoety@commonswiki Tiptoety@enwiki Tiptoety@metawiki Titore@itwiki Tks4Fish@enwiki Tks4Fish@loginwiki Tks4Fish@ptwiki Tnxman307@enwiki ToBeFree@enwiki Tom Morris@enwikinews Tomer T@hewiki TonyBallioni@enwiki Toto Azéro@frwiki Triglav@jawiki Trijnstel@commonswiki Trijnstel@enwiki Trijnstel@loginwiki Trijnstel@metawiki TunnelESON@enwikiquote TunnelESON@fawiki TunnelESON@frwiktionary TunnelESON@jawikibooks TunnelESON@jawiktionary TunnelESON@kowiki TunnelESON@kowikisource TunnelESON@metawiki TunnelESON@mlwiki TunnelESON@ptwiki TunnelESON@ruwiki TunnelESON@siwiki TunnelESON@thwiki TunnelESON@ukwiki TunnelESON@viwiki TunnelESON@zhwiki Tznkai@enwiki Ugur Basak@trwiki Uncitoyen@trwiki UninvitedCompany@enwiki Ustad abu gosok@idwiki VIGNERON@loginwiki VWalters-WMF@enwiki VWalters-WMF@testwiki Vanamonde93@enwiki Vanished user 5zariu3jisj0j4irj@enwiki Vassyana@enwiki Vermont@loginwiki Vermont@metawiki Vermont@simplewiki Versageek@enwiki Versageek@enwiktionary VictorAnyakin@ukwiki Vigorous action@jawiki Vincent Vega@trwiki Viniciusmc@ptwiki Vituzzu@enwiki Vituzzu@eswikiquote Vituzzu@itwiki Vituzzu@metawiki Vituzzu@ptwikibooks Vlad@rowiki Vodomar@hrwiki W.CC@jawiki WBrown (WMF)@test2wiki WBrown (WMF)@testcommonswiki WBrown (WMF)@testwiki WMFOffice@arwiki WMFOffice@commonswiki WMFOffice@enwiki WMFOffice@fawiki WMFOffice@frwiki WMFOffice@loginwiki WMFOffice@mediawikiwiki WMFOffice@metawiki WMFOffice@ruwiki WMFOffice@wikidatawiki Wagino 20100516@idwiki Walter@brwiki Walter@nlwiki Walter@nlwikibooks Walter@testwiki WarX@plwiki Wegge@dawiki Werdna@enwiki Werdna@enwikisource Werdna@mediawikiwiki Whiteknight@enwikibooks Wiki13@loginwiki Wikimol@cswiki Wikitanvir@bnwiki WilliamH@enwiki Wim b@loginwiki Wind@ruwiki Wizardman@enwiki Wojciech Pędzich@betawikiversity Wojciech Pędzich@commonswiki Wojciech Pędzich@dewiktionary Wojciech Pędzich@eewiki Wojciech Pędzich@enwikiquote Wojciech Pędzich@enwikiversity Wojciech Pędzich@fawiki Wojciech Pędzich@fiwikibooks Wojciech Pędzich@itwikinews Wojciech Pędzich@kowiki Wojciech Pędzich@ltwiktionary Wojciech Pędzich@nlwikimedia Wojciech Pędzich@nowiki Wojciech Pędzich@outreachwiki Wojciech Pędzich@plwiki Wojciech Pędzich@plwikibooks Wojciech Pędzich@plwikimedia Wojciech Pędzich@plwikinews Wojciech Pędzich@plwikiquote Wojciech Pędzich@plwiktionary Wojciech Pędzich@rowiki Wojciech Pędzich@simplewiktionary Wojciech Pędzich@szlwiki Wojciech Pędzich@ukwiki Wojciech Pędzich@xhwiki Worm That Turned@enwiki Wugapodes@enwiki Wulfson@ruwiki XXBlackburnXx@enwiki XXBlackburnXx@loginwiki XXBlackburnXx@nlwiki Xania@enwikibooks Xaosflux@loginwiki Xaosflux@metawiki Xeno@enwiki Y-dash@jawiki Yahya@loginwiki Yamla@enwiki Yann@arwiki Yann@commonswiki Yann@enwikisource Yann@frwikinews Yann@hiwiki Yann@itwikiversity Yann@metawiki Yann@sourceswiki Yann@ukwiki YellowMonkey@enwiki Yunshui@enwiki Z1720@enwiki ZExley (WMF)@enwiki ZSoo (WMF)@enwiki Zabe@enwiki Zabe@mediawikiwiki Zabe@test2wiki Zabe@testcommonswiki Zabe@testwiki Zafer@trwikimedia Zzuuzz@enwiki Érico@ptwiki Боки@srwiki Ле Лой@ruwiki Обрадовић Горан@srwiki Стефанко1982@ukwiki בריאן@hewiki דגש@hewiki דולב@hewiki דניאל ב.@hewiki יונה בנדלאק@hewiki ירון@hewiki מוטי@hewiki מתניה@zhwiki עוזי ו.@hewiki עידו@hewiki עli@hewiki רחל1@hewiki باسم@arwiki جار الله@arwiki صالح@arwiki علاء@arwiki علاء@enwiki علاء@loginwiki علاء@wikidatawiki فيصل@arwiki ميموني@arwiki আফতাবuজ্জামান@bnwiki さかおり@jawiki 柏尾菓子@jawiki 欅@jawiki 海獺@jawiki 이강철@kowiki `; // PASTE YOUR LIST HERE function getDomain(db) { const mapping = { 'metawiki': 'meta.wikimedia.org', 'commonswiki': 'commons.wikimedia.org', 'wikidatawiki': 'www.wikidata.org', 'loginwiki': 'login.wikimedia.org', 'sourceswiki': 'wikisource.org', 'specieswiki': 'species.wikimedia.org', 'incubatorwiki': 'incubator.wikimedia.org' }; if (mapping[db]) return mapping[db]; const projects = ['wiktionary', 'wikibooks', 'wikiquote', 'wikinews', 'wikiversity', 'wikivoyage']; for (const p of projects) { if (db.endsWith(p)) return `${db.replace(p, '').replace(/_/g, '-')}.${p}.org`; } return `${db.replace('wiki', '').replace(/_/g, '-')}.wikipedia.org`; } function initInterface() { document.title = "GCUS Data Generator"; const $body = $('#mw-content-text'); $body.empty(); $body.append(` <div style="background:white; border:1px solid #a2a9b1; padding:20px; font-family:sans-serif; color:#202122;"> <h1 style="margin-top:0; border-bottom:1px solid #a2a9b1; padding-bottom:10px;">GCUS Data Generator v6.9.2</h1> <p>Generates Lua data for <code>Module:GlobalRoles/Data</code>. <br> <b>Filter:</b> Local CU < 60 days ignored. <b>Format:</b> YYYY-MM-DD.</p> <div style="display:flex; gap:20px; margin-bottom:20px;"> <div style="flex:1;"> <strong>Input List:</strong> <textarea id="gcusInput" style="width:100%; height:200px; font-family:monospace; font-size:12px; border:1px solid #a2a9b1; margin-top:5px;">${USERS_LIST}</textarea> </div> <div style="flex:1;"> <strong>Log:</strong> <div id="gcusLog" style="height:200px; overflow-y:scroll; background:black; color:#0f0; font-family:monospace; font-size:11px; padding:5px; border-radius:3px; margin-top:5px;">Ready...</div> </div> </div> <button id="gcusStart" style="padding:10px 24px; background:#36c; color:white; border:none; border-radius:2px; cursor:pointer; font-weight:bold;">START SCAN</button> <span id="gcusStatus" style="margin-left:15px; font-weight:bold; color:#555;"></span> <div id="gcusResultArea" style="display:none; margin-top:20px;"> <strong>Lua Output:</strong><br> <textarea id="gcusOutput" style="width:100%; height:400px; font-family:monospace; font-size:12px; background:#fffbe6; border:1px solid #f2e394; margin-top:5px;"></textarea> </div> </div> `); $('#gcusStart').on('click', runGenerator); } async function runGenerator() { const input = $('#gcusInput').val(); const $log = $('#gcusLog'); const $output = $('#gcusOutput'); const $status = $('#gcusStatus'); const $btn = $('#gcusStart'); $btn.prop('disabled', true).css('background', '#a2a9b1').text('SCANNING...'); $log.empty(); $('#gcusResultArea').show(); const log = (msg) => { $log.append($('<div>').text(`[${new Date().toLocaleTimeString()}] ${msg}`)); $log.scrollTop($log[0].scrollHeight); }; const sleep = ms => new Promise(r => setTimeout(r, ms)); async function safeFetch(url) { await sleep(1000); const fullUrl = url + "&origin=*&contact=User:MrJaroslavik"; try { const response = await fetch(fullUrl); if (response.status === 429) { const retry = response.headers.get('Retry-After') || 60; log(`Rate limit! Waiting ${retry}s...`); await sleep(retry * 1000); return safeFetch(url); } return await response.json(); } catch (e) { log(`Error: ${e.message}. Retrying...`); await sleep(3000); return safeFetch(url); } } function checkChange(logEntry, roleName) { const p = logEntry.params; if (!p) return null; const r = roleName.toLowerCase(); const normalize = (v) => Array.isArray(v) ? v.join(',').toLowerCase() : String(v || "").toLowerCase(); const oldStr = normalize(p.oldGroups || p.oldgroups || p.remove || p[0]); const newStr = normalize(p.newGroups || p.newgroups || p.add || p[1]); const hasRole = (s) => new RegExp('(^|,|\\s)' + r + '($|,|\\s)').test(s); if (!hasRole(oldStr) && hasRole(newStr)) return "added"; if (hasRole(oldStr) && !hasRole(newStr)) return "removed"; return null; } function getDaysDiff(dateFrom, dateTo) { if (dateTo === 'present') return 999; const d1 = new Date(dateFrom); const d2 = new Date(dateTo); return Math.floor((d2 - d1) / (1000 * 60 * 60 * 24)); } const localArray = input.split('\n').map(s => s.trim()).filter(s => s.length > 0); const globalArray = [...new Set(localArray.map(s => { const parts = s.split('@'); parts.pop(); return parts.join('@'); }))]; let rawData = {}; log(`Started. Processing ${globalArray.length} unique users.`); // 1. GLOBAL ROLES for (let i = 0; i < globalArray.length; i++) { const name = globalArray[i]; const gDb = 'global'; $status.text(`Global Rights: ${i+1}/${globalArray.length} (${name})`); let continueToken = ''; while (continueToken !== undefined) { const data = await safeFetch(`https://meta.wikimedia.org/w/api.php?action=query&list=logevents&letype=gblrights&letitle=User:${encodeURIComponent(name)}&lelimit=max${continueToken}&format=json`); const logs = (data.query && data.query.logevents ? data.query.logevents : []).reverse(); logs.forEach(l => { const date = l.timestamp.substring(0, 10); ['steward', 'staff', 'ombuds'].forEach(role => { const change = checkChange(l, role); if (change === "added") { if (!rawData[name]) rawData[name] = []; rawData[name].push({ role, db: gDb, from: date, to: 'present' }); } else if (change === "removed") { const entries = rawData[name] || []; const last = entries.filter(e => e.role === role && e.to === 'present').pop(); if (last) last.to = date; } }); }); continueToken = data.continue ? `&lecontinue=${data.continue.lecontinue}` : undefined; } } // 2. LOCAL ROLES for (let i = 0; i < localArray.length; i++) { const parts = localArray[i].split('@'); const lDb = parts.pop(); const name = parts.join('@'); // FIXED LINE 164 (removal of ?.) const userEntries = rawData[name] || []; if (lDb === 'loginwiki' && userEntries.some(e => e.db === 'global' && e.role === 'steward')) continue; $status.text(`Local Rights: ${i+1}/${localArray.length} (${name}@${lDb})`); let continueToken = ''; const domain = getDomain(lDb); try { while (continueToken !== undefined) { const data = await safeFetch(`https://${domain}/w/api.php?action=query&list=logevents&letype=rights&letitle=User:${encodeURIComponent(name)}&lelimit=max${continueToken}&format=json`); const logs = (data.query && data.query.logevents ? data.query.logevents : []).reverse(); logs.forEach(l => { const date = l.timestamp.substring(0, 10); const change = checkChange(l, 'checkuser'); if (change === "added") { if (!rawData[name]) rawData[name] = []; rawData[name].push({ role: 'cu', db: lDb, from: date, to: 'present' }); } else if (change === "removed") { const entries = rawData[name] || []; const last = entries.filter(e => e.role === 'cu' && e.db === lDb && e.to === 'present').pop(); if (last) last.to = date; } }); continueToken = data.continue ? `&lecontinue=${data.continue.lecontinue}` : undefined; } } catch(e) { log(`Error ${name}@${lDb}: ${e.message}`); } } // 3. LUA OUTPUT let lua = "return {\n users = {\n"; const sortedUsers = Object.keys(rawData).sort(); for (const user of sortedUsers) { const filteredEntries = rawData[user].filter(e => { if (e.db === 'global') return true; if (e.to === 'present') return true; return getDaysDiff(e.from, e.to) >= 60; }); if (filteredEntries.length > 0) { lua += ` ["${user}"] = {\n`; filteredEntries.forEach(e => { lua += ` { role = "${e.role}", db = "${e.db}", from = "${e.from}", to = "${e.to}" },\n`; }); lua += ` },\n`; } } lua += " }\n}"; $output.val(lua); $status.text("FINISHED!"); log("Scan complete."); $btn.prop('disabled', false).css('background', '#36c').text('RUN AGAIN'); } $(initInterface); })(); /* </nowiki> */ ngjd8m1ejw5j7far83yi4z9x57pokl3 739777 739775 2026-04-29T14:48:53Z MrJaroslavik 44012 + 739777 javascript text/javascript /* <nowiki> */ /** * GCUS Data Generator (function() { if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || mw.config.get('wgTitle').split('/').pop() !== 'GCUS-Data') { return; } const USERS_LIST = `.anaconda@kvwiki .anaconda@tiwiki .mau.@itwiki 0x010C@frwiki 1234qwer1234qwer4@loginwiki 1997kB@wikidatawiki 3(MG)²@frwiki A09@loginwiki A09@slwiki ABX@plwiki AJones (WMF)@enwiki AJones (WMF)@metawiki AKarani (WMF)@enwiki AKoval (WMF)@enwiki ATripathi-WMF@enwiki ATripathi-WMF@eswiki Aaron Schulz@enwiki Abisys@itwiki Acagastya@commonswiki Acagastya@enwikinews Adailton@ptwiki AdiJapan@rowiki Adler.fa@fawiki Adrian@skwiki Adrignola@enwikibooks Agony@fiwiki Ajraddatz@commonswiki Ajraddatz@enwiki Ajraddatz@eswiki Ajraddatz@loginwiki Ajraddatz@metawiki Akeron@frwiki Akoopal@nlwiki Alain r@frwiki Alan@commonswiki Alan@eswiki Alan@metawiki Albertoleoncio@loginwiki Albertoleoncio@ptwiki Aldnonymous@idwiki Alex Shih@enwiki Alexander Misel@zhwiki Alexanderps@ptwiki Alexanderps@ptwikinews Alhen@eswiki Alison@enwiki Alnokta@arwiki Alphax@commonswiki Alraunenstern@dewiki Amalthea@enwiki AmandaNP@enwiki AmandaNP@hrwiki AmandaNP@itwiki AmandaNP@loginwiki AmandaNP@wikidatawiki Andre Engels@acewiki Andre Engels@anwiki Andre Engels@barwiki Andre Engels@bgwiki Andre Engels@bswiki Andre Engels@dewiki Andre Engels@eswiktionary Andre Engels@fawiki Andre Engels@fawikiquote Andre Engels@kowiki Andre Engels@lvwiki Andre Engels@mswiki Andre Engels@nlwiki Andre Engels@ptwiki Andre Engels@ptwiktionary Andre Engels@sourceswiki Andre Engels@specieswiki Andre Engels@testwiki Andre Engels@urwiki Andre Engels@viwiki Andre Engels@zeawiki Andre Engels@zhwiki Andrei Stroe@rowiki Andrej Šalov@hrwiki Andriy.v@ukwiki Annabel@nlwiki Anr@fiwiki Anthere@enwiki Anthere@metawiki AntiCompositeNumber@loginwiki Antime@arwiki Aoidh@enwiki Aoineko@frwiki Aphaia@enwikiquote AramilFeraxa@loginwiki AramilFeraxa@metawiki AramilFeraxa@plwiki Aratal@frwiki Arcticocean@enwiki Arcticocean@hrwiki Arcticocean@kowiki Arcticocean@ptwiki Arcticocean@ukwiki Argo Navis@hrwiki Ash Crow@frwiki Asilvering@enwiki Ask21@itwiki Ausir@plwiki Avocato@arwiki Avraham@enwiki Avraham@loginwiki Azafred@enwiki B20180@thwiki BDD@enwiki BRPever@loginwiki BRPever@simplewiki BRPever@wikidatawiki Bahamut0013@enwiki Barak a@hewiki Barcex@eswiki Barkeep49@enwiki Barras@enwiki Barras@loginwiki Barras@metawiki Barras@simplewiki Base@loginwiki Bastique@azwiki Bastique@commonswiki Bastique@crwiki Bastique@enwiki Bastique@enwikibooks Bastique@enwikiquote Bastique@enwikiversity Bastique@eswiki Bastique@nowiki Bastique@plwiki Bastique@ruwiki Bastique@simplewiki Bastique@stwiki Bastique@svwiki Bastique@testwiki Bastique@trwiki Bastique@wikimania2008wiki Bastique@yiwiki Bastique@zhwiki Bbb23@enwiki Bdk@dewiki Beeblebrox@enwiki Bellcricket@jawiki Bencmq@zhwiki Benevolent Otter@plwiki Bennylin@idwiki Benoît Prieur@frwiki Berean Hunter@enwiki Beria@ptwiki Bernard@eswiki BetoCG@eswiki Billinghurst@enwikisource Billinghurst@eswikivoyage Billinghurst@loginwiki Billinghurst@metawiki Billinghurst@plwikinews Billinghurst@slwiki Bináris2@huwiki Bináris@huwiki Biologo32@ptwiki Blablubbs@enwiki BlackBeast@eswiki Borgx@idwiki Bradv@enwiki Brandon@enwiki BraneJ@srwiki Brian McNeil@enwikinews Brian@enwikinews Brooke Vibber@dewiki Brooke Vibber@enwiki Brooke Vibber@frwiki Brooke Vibber@mediawikiwiki Brooke Vibber@testwiki Bryan@commonswiki BryanDavis@labswiki Bsadowski1@loginwiki Bsadowski1@metawiki Bsadowski1@simplewiki CAshraf (WMF)@enwiki CLo (WMF)@testwiki CMaslak (WMF)@enwiki CMaslak (WMF)@fawiki CMaslak (WMF)@srwiki CMaslak (WMF)@uzwiki CPettet (WMF)@labswiki CSteigenberger (WMF)@cawiki CSteigenberger (WMF)@enwiki CSteigenberger (WMF)@jawiki CSteigenberger (WMF)@metawiki CSteipp (WMF)@ptwiktionary Cabayi@enwiki Caesar@svwiki Callanecc@enwiki CaptainEek@enwiki Carkuni@jawiki Cartman02au@metawiki Cary Bass@arwiki Cary Bass@aswiki Cary Bass@commonswiki Cary Bass@enwiki Cary Bass@enwikiquote Cary Bass@enwikiversity Cary Bass@frwiki Cary Bass@hiwiki Cary Bass@itwiki Cary Bass@metawiki Cary Bass@ptwiki Casliber@enwiki Cassiopeia sweet@jawiki Cato@enwikiquote Cdip150@zhwiki Cgt@dawiki Chaos@arwiki Chase me ladies, I'm the Cavalry@enwiki Chatama@jawiki Christian List@dawiki Christine (WMF)@enwiki Chuck Entz@enwiktionary Cinabrium@eswiki Ciphers@arwiki Cirdan@dewiki Cirt@enwikinews Civvi~itwiki@itwiki Clem23@frwiki Codc@dewiki CodeMonk@ruwiki Coet@cawiki Conde Edmond Dantès@ptwiki Connel MacKenzie@enwiktionary Cool Hand Luke@enwiki Coren@enwiki Coren@labswiki Count Count@dewiki Count Count@loginwiki Courcelles@enwiki Creol@simplewiki Cromium@elwiki Cromium@enwikinews Cromium@loginwiki Cromium@rowiki Cromium@sqwiki Cromium@zhwiki Cruccone@itwiki Csigabi@huwiki Cspurrier@enwikinews Cspurrier@enwikiquote Cspurrier@enwikiversity Cspurrier@fawiki Cspurrier@itwiki Cspurrier@metawiki Cspurrier@ptwiki Cspurrier@simplewiki Cspurrier@ukwiki Cspurrier@zhwiki Céréales Killer@frwiki DBarratt (WMF)@testwiki DGG@enwiki DHN@viwiki DJackson (WMF)@testwiki DMaza (WMF)@testwiki DR@ruwiki DWalden (WMF)@frwiki DWalden (WMF)@testwiki Daedalus@svwiki Daimore@ptwiki Damzow@hewiki Dan Koehl@specieswiki Daniel Quinlan@enwiki Daniel@enwiki Daniuu@arwiki Daniuu@loginwiki Daniuu@metawiki Daniuu@nlwiki Daniuu@testwiki Danmichaelo@nowiki DannyH (WMF)@mediawikiwiki DannyH (WMF)@testwiki DannyS712@testwiki Danu Widjajanto@idwiki Darkoneko@aswiki Darkoneko@brwiki Darkoneko@bugwiki Darkoneko@cswiki Darkoneko@cswikisource Darkoneko@cswiktionary Darkoneko@dawiki Darkoneko@enwiki Darkoneko@enwikiversity Darkoneko@eswiki Darkoneko@fawiki Darkoneko@frwiki Darkoneko@frwikiquote Darkoneko@glwiki Darkoneko@huwiki Darkoneko@jawiktionary Darkoneko@mediawikiwiki Darkoneko@metawiki Darkoneko@mlwiki Darkoneko@newiki Darkoneko@nlwikiquote Darkoneko@ruwiktionary Darkoneko@skwiki Darkoneko@skwikisource Darkoneko@sourceswiki Darkoneko@srwiki Darkoneko@stwiki Darkoneko@testwiki Darkoneko@trwiki Darkoneko@ukwiki Darkoneko@viwiki Darkoneko@warwiki Darkoneko@wawiki Darkoneko@wowiki Darkoneko@yiwiki DatGuy@enwiki Datrio@fawiki David Fuchs@enwiki David Gerard@enwiki David Wadie Fisher-Freberg@idwiki Dbeef@enwiki Dbl2010@azwiki Dbl2010@kowiktionary Dbl2010@testwiki Dbl2010@trwiki Dbl2010@vlswiki Dcastor@svwiki Deineka@ukwiki Demicx@bswiki Der-Wir-Ing@dewiki DerHexer@loginwiki DerHexer@metawiki Derbeth@enwikibooks Deror avi@hewiki Deskana (WMF)@enwiki Deskana (WMF)@mediawikiwiki Deskana@enwiki Dferg@acewiki Dferg@bat_smgwiki Dferg@bewiki Dferg@bgwiki Dferg@brwiki Dferg@cebwiki Dferg@enwikiversity Dferg@eowiki Dferg@extwiki Dferg@fawiki Dferg@frwikiversity Dferg@gdwiki Dferg@glwiktionary Dferg@hawwiki Dferg@hiwiki Dferg@hywiki Dferg@iswiki Dferg@itwikiquote Dferg@jawikiquote Dferg@kawiki Dferg@kowiki Dferg@kshwiki Dferg@lawiki Dferg@lvwiki Dferg@mkwiktionary Dferg@mswiki Dferg@ndswiki Dferg@orwiktionary Dferg@pmswiki Dferg@ptwikibooks Dferg@ptwikisource Dferg@ptwiktionary Dferg@quwiki Dferg@rowiki Dferg@siwiki Dferg@specieswiki Dferg@sqwiki Dferg@swwiki Dferg@tewiki Dferg@ukwiki Dferg@uzwiki Dferg@viwiki Dferg@vowiki Dferg@wuuwiki Dferg@xalwiki Dferg@xhwiki Dferg@zh_classicalwiki Dferg@zh_yuewiki Dferg@zhwikinews Djordjes@srwiki Djsasso@simplewiki Dmcdevit@wikimania2008wiki Dmitry Gerasimov@ruwiki Dmthoth@kowiki DoRD@enwiki Dominic@enwiki Dominic@enwiktionary Doug Weller@enwiki Doğu@commonswiki Doğu@itwiki Dr-Taher@arwiki Dragoniez@jawiki Drahreg01@dewiki Drbug@ruwiki Dreamy Jazz@enwiki Dreamy Jazz@testwiki Drini@aawiki Drini@afwiki Drini@anwiki Drini@astwiki Drini@azwiki Drini@bgwiki Drini@brwiki Drini@cywiki Drini@dawikibooks Drini@dsbwiki Drini@enwikibooks Drini@enwikiquote Drini@enwikisource Drini@enwikiversity Drini@eowiki Drini@eswiki Drini@etwiki Drini@euwiktionary Drini@extwiki Drini@fawiki Drini@fiwiki Drini@fiwikibooks Drini@fiwikinews Drini@fiwikisource Drini@frpwiki Drini@frwikiquote Drini@gawiki Drini@glwiki Drini@hakwiki Drini@hiwiki Drini@idwiki Drini@iewiki Drini@igwiki Drini@incubatorwiki Drini@iswiki Drini@iswikibooks Drini@itwikisource Drini@itwikisource Drini@jvwiki Drini@kmwiki Drini@kowikiquote Drini@lawiki Drini@lbewiki Drini@lgwiki Drini@lmowiki Drini@lowiki Drini@lvwiki Drini@mediawikiwiki Drini@mlwiki Drini@mswiki Drini@ngwiki Drini@nlwikiquote Drini@nowiki Drini@nowikisource Drini@nvwiki Drini@piwiki Drini@rnwiki Drini@rowiki Drini@ruwiki Drini@ruwikinews Drini@ruwikiquote Drini@scnwiktionary Drini@sgwiki Drini@shwiki Drini@skwiki Drini@snwiki Drini@sowiki Drini@sqwiki Drini@srwiki Drini@swwiki Drini@testwiki Drini@tlwiki Drini@towiki Drini@ukwiki Drini@ukwikinews Drini@ukwiktionary Drini@viwiki Drini@wuuwiki Drini@xalwiki Drini@xhwiki Drini@yiwiki Drini@zh_yuewiki Drini@zhwiki Drmies@enwiki Dtom@hrwiki Dungodung@metawiki Dungodung@srwiki Dungodung@testwiki Durifon@frwiki Dyolf77@arwiki Dyolf77@frwiki Dzordzm@srwiki E.coli@hrwiki EMill-WMF@enwiki EPIC@enwiki EPIC@loginwiki EPIC@metawiki EVula@enwikiquote EdJohnston@enwiki Edmenb@eswiki Effeietsanders@dewiktionary Effeietsanders@enwikiquote Effeietsanders@enwikiversity Effeietsanders@fawiki Effeietsanders@kowiki Effeietsanders@kshwiki Effeietsanders@kuwiki Effeietsanders@nowiki Effeietsanders@ptwiki Effeietsanders@testwiki Einsbor@enwiki Einsbor@loginwiki Ejs-80@fiwiki Elcobbola@commonswiki Eldarion@trwiki Elen of the Roads@enwiki Elfix@cywiktionary Elfix@dewikibooks Elfix@frwiki Elfix@idwikiquote Elfix@itwikinews Elfix@loginwiki Elfix@mywiki Elfix@ptwikibooks Elfix@ptwikiquote Elfix@ptwikisource Elian@dewiki Elli@enwiki Ellywa@nlwiki Elmacenderesi@trwiki Elockid@enwiki Elph@arwiki Elton@astwiki Elton@cebwiki Elton@chwiki Elton@incubatorwiki Elton@jawikinews Elton@loginwiki Elton@mediawikiwiki Elton@plwikiquote Elton@ptwikiquote Elton@shwiki Elton@testwiki Elton@wikidatawiki Elton@zhwiktionary Elwood@itwiki Emir Kotromanić@bswiki Emufarmers@enwiki Emufarmers@eswiki Emufarmers@metawiki Emx@bswiki Enterprisey@enwiki Epinheiro@ptwiki Eptalon@simplewiki Erkan Yilmaz@enwikiversity Erwin85@zhwiki Erwin@nlwiki Essjay@enwiki Eta Carinae@ptwiki Euryalus@enwiki Eusebius@commonswiki EvgenyGenkin@ruwiki Ex13@hrwiki FT2@enwiki Faendalimas@specieswiki Faso@jawiki FayssalF@enwiki Felipe da Fonseca@ptwiki Ferret@enwiki Filzstift@dewiki Firefly@enwiki Fitindia@commonswiki FloNight@enwiki Fluff@svwiki FoBe@huwiki Fr33kman@simplewiki Frank@enwiki Fred Bauder@enwiki Freetrashbox@jawiki Fritzpoll@enwiki Gac@itwiki Galahad@eswiki Galahad@fawiki Galahad@trwiki Gamaliel@enwiki GeneralNotability@enwiki Geonuch@thwiki Giraffer@enwiki Girth Summit@enwiki Gmaxwell@commonswiki Gnom@dewiki Goo3@ukwiki GorillaWarfare@enwiki Gpvos@nlwiki Gribeco@frwiki Grin@huwiki Groucho NL@nlwiki Guerillero@enwiki Guillom@arwiki Guillom@cvwiki Guillom@dawiki Guillom@fawiki Guillom@fiwikiquote Guillom@foundationwiki Guillom@frwiki Guillom@frwikinews Guillom@frwikisource Guillom@frwiktionary Guillom@iawiki Guillom@ilowiki Guillom@iowiki Guillom@iswiki Guillom@itwikinews Guillom@kabwiki Guillom@kowiki Guillom@rowiki Guillom@siwiktionary Guillom@trwiki Gutza@rowiki Gvf@itwiki Góngora@cawiki HJ Mitchell@enwiki HVL@ptwiki Ha98574@kowiki HaeB@dewiki HaithamS (WMF)@enwiki HakanIST@loginwiki Hardscarf@nlwiki Harel@hewiki Haros@nowiki Harriv@fiwiki Hasley@barwiki Hasley@loginwiki Hasley@metawiki Hayabusa future@idwiki Hei ber@dewiki Hei ber@enwiki Hephaion@dewiki Herbythyme@commonswiki Herbythyme@enwikibooks Herbythyme@metawiki Herbythyme@nowiki Hersfold@enwiki Hexasoft@frwiki Hidayatsrf@idwiki Hidro@hewiki Hispa@eswiki Hjvannes@nlwiki Hoo man@loginwiki HouseBlaster@enwiki Huji@fawiki Hunyadym@huwiki Hyméros@frwiki INeverCry@commonswiki IRTC1015@kowiki Ilya Voyager@ruwiki IlyaHaykinson@enwikinews Indech@ptwiki Infinite0694@eswiki Infinite0694@jawiki Infinite0694@loginwiki Infinite0694@metawiki Iridescent@enwiki IvanLanin@idwiki Ivanvector@enwiki Izno@enwiki J.delanoy@enwiki JAbrams (WMF)@cebwiki JAbrams (WMF)@commonswiki JAbrams (WMF)@cswiktionary JAbrams (WMF)@enwiki JAbrams (WMF)@eowiki JAbrams (WMF)@frwiki JAbrams (WMF)@jawiki JAbrams (WMF)@metawiki JAbrams (WMF)@ptwiki JAbrams (WMF)@wikidatawiki JCrespo (WMF)@labswiki JEissfeldt (WMF)@dewiki JEissfeldt (WMF)@enwiki JEissfeldt (WMF)@jawiki JJMC89@eswiki JJMC89@loginwiki JSutherland (WMF)@afwiki JSutherland (WMF)@arwiki JSutherland (WMF)@arwikisource JSutherland (WMF)@azwiki JSutherland (WMF)@bgwiki JSutherland (WMF)@commonswiki JSutherland (WMF)@cswiki JSutherland (WMF)@dawiki JSutherland (WMF)@dewiki JSutherland (WMF)@enwiki JSutherland (WMF)@enwikibooks JSutherland (WMF)@enwikiquote JSutherland (WMF)@enwiktionary JSutherland (WMF)@eswiki JSutherland (WMF)@fawiki JSutherland (WMF)@frwiki JSutherland (WMF)@frwikiquote JSutherland (WMF)@frwikiversity JSutherland (WMF)@hewiki JSutherland (WMF)@hrwiki JSutherland (WMF)@huwiki JSutherland (WMF)@hywiki JSutherland (WMF)@idwiki JSutherland (WMF)@itwiki JSutherland (WMF)@jawiki JSutherland (WMF)@kowiki JSutherland (WMF)@loginwiki JSutherland (WMF)@mediawikiwiki JSutherland (WMF)@metawiki JSutherland (WMF)@nlwiki JSutherland (WMF)@nowiki JSutherland (WMF)@plwiki JSutherland (WMF)@ptwiki JSutherland (WMF)@ruwiki JSutherland (WMF)@simplewiki JSutherland (WMF)@svwiki JSutherland (WMF)@testwiki JSutherland (WMF)@ukwiki JSutherland (WMF)@wikidatawiki JSutherland (WMF)@zhwiki JWSchmidt@enwikiversity JWang (WMF)@testwiki Jafeluv@fiwiki Jagro@cswiki Jake Park@eswiki Jalexander-WMF@arwiki Jalexander-WMF@azwiki Jalexander-WMF@bgwiki Jalexander-WMF@cawiki Jalexander-WMF@commonswiki Jalexander-WMF@cswiki Jalexander-WMF@dawiki Jalexander-WMF@dewiki Jalexander-WMF@enwiki Jalexander-WMF@enwikibooks Jalexander-WMF@enwikiquote Jalexander-WMF@enwikisource Jalexander-WMF@enwikiversity Jalexander-WMF@enwikivoyage Jalexander-WMF@enwiktionary Jalexander-WMF@eswiki Jalexander-WMF@eswikivoyage Jalexander-WMF@frwiki Jalexander-WMF@gawiki Jalexander-WMF@hewiki Jalexander-WMF@hewikisource Jalexander-WMF@incubatorwiki Jalexander-WMF@iswiki Jalexander-WMF@itwiki Jalexander-WMF@kowiki Jalexander-WMF@kowikibooks Jalexander-WMF@kowikisource Jalexander-WMF@loginwiki Jalexander-WMF@mediawikiwiki Jalexander-WMF@metawiki Jalexander-WMF@mywiki Jalexander-WMF@nowiki Jalexander-WMF@nowiktionary Jalexander-WMF@plwiki Jalexander-WMF@ptwiki Jalexander-WMF@ruwiki Jalexander-WMF@ruwikibooks Jalexander-WMF@ruwikisource Jalexander-WMF@simplewiki Jalexander-WMF@specieswiki Jalexander-WMF@svwiki Jalexander-WMF@testwiki Jalexander-WMF@wikidatawiki Jalexander-WMF@zhwiki Jalexander@foundationwiki Jalexander@labswiki James LL@loginwiki Jameslwoodward@commonswiki Jamesofur@simplewiki Jamie Tubers@enwiki Janorovic Volkov@idwiki Japiot@nlwiki Jasper Deng@wikidatawiki Jayjg@enwiki Jbribeiro1@ptwiki Jcb@nlwiki Jclemens@enwiki Jdforrester (WMF)@enwiki Jdforrester (WMF)@enwikiquote Jdforrester@enwiki Jean-Christophe BENOIST@frwiki Jeffq@enwikiquote Jimbo Wales@enwiki Jimmy Xu@zhwiki Jmrebes@cawiki Jniemenmaa@fiwiki Joe Roe@enwiki Johannnes89@loginwiki Johannnes89@metawiki John Vandenberg@enwiki John Vandenberg@enwikisource Jon Harald Søby@akwiki Jon Harald Søby@alswiki Jon Harald Søby@cawiki Jon Harald Søby@cswiki Jon Harald Søby@dewiki Jon Harald Søby@enwiki Jon Harald Søby@enwikiquote Jon Harald Søby@fawiki Jon Harald Søby@fiwikibooks Jon Harald Søby@fiwikisource Jon Harald Søby@frwiki Jon Harald Søby@iswiki Jon Harald Søby@itwiki Jon Harald Søby@kowiki Jon Harald Søby@metawiki Jon Harald Søby@nnwiki Jon Harald Søby@nowiki Jon Harald Søby@nowikibooks Jon Harald Søby@plwikibooks Jon Harald Søby@plwikinews Jon Harald Søby@plwiktionary Jon Harald Søby@ruwiki Jon Harald Søby@skwiki Jon Harald Søby@svwiki Jon Harald Søby@svwikinews Jon Harald Søby@testwiki Jon Harald Søby@trwiki Jon Harald Søby@yiwiki Jon Kolbert@loginwiki Joutbis@cawiki Jpgordon@enwiki Jrogers (WMF)@metawiki Julle@svwiki Julle@testwiki Just Sayori@thwiki JusteJuju10@frwiki Jyothis@eswiktionary Jyothis@loginwiki Jyothis@metawiki KColeman-WMF@testwiki KHarlan (WMF)@loginwiki KHarlan (WMF)@testwiki KLevan (WMF)@arwiki KLevan (WMF)@commonswiki KLevan (WMF)@enwiki KLevan (WMF)@hewiki KLevan (WMF)@idwiki KLevan (WMF)@metawiki KMT@jawiki KRLS@cawiki Kaare@dawiki Kal-El~bswiki@bswiki Kalliope (WMF)@azwiki Kalliope (WMF)@commonswiki Kalliope (WMF)@dewiki Kalliope (WMF)@enwiki Kalliope (WMF)@enwikiversity Kalliope (WMF)@enwiktionary Kalliope (WMF)@eswiki Kalliope (WMF)@frwiki Kalliope (WMF)@frwiktionary Kalliope (WMF)@idwiki Kalliope (WMF)@itwiki Kalliope (WMF)@jawiki Kalliope (WMF)@loginwiki Kalliope (WMF)@metawiki Kalliope (WMF)@outreachwiki Kalliope (WMF)@testwiki Kalliope (WMF)@zhwiki Kanjy@jawiki Karol007@plwiki Karsten11@dewiki Kbrown (WMF)@arwiki Kbrown (WMF)@commonswiki Kbrown (WMF)@dewiki Kbrown (WMF)@enwiki Kbrown (WMF)@enwikibooks Kbrown (WMF)@enwikiversity Kbrown (WMF)@enwikivoyage Kbrown (WMF)@eowiki Kbrown (WMF)@eswiki Kbrown (WMF)@eswikibooks Kbrown (WMF)@frwiki Kbrown (WMF)@frwiktionary Kbrown (WMF)@itwiki Kbrown (WMF)@loginwiki Kbrown (WMF)@metawiki Kbrown (WMF)@ptwiki Kbrown (WMF)@simplewiki Kbrown (WMF)@testwiki Kbrown (WMF)@trwiki Kbrown (WMF)@zhwiki Keegan@enwiki Kegns@zhwiki Kelapstick@enwiki Kiran Gopi@mlwiki Kirill Lokshin@enwiki KnightLago@enwiki KnudW@dawiki Koavf@specieswiki KonstantinaG07@loginwiki KovacsUr@huwiki KrakatoaKatie@enwiki Krd@commonswiki Krd@loginwiki Krd@metawiki Ks aka 98@jawiki Ks0stm@enwiki Kulac@dewiki Kv75@ruwiki Kylu@enwiki Kylu@metawiki L235@enwiki LD@frwiki LFaraone@enwiki Laaknor@metawiki Laaknor@nowiki Ladsgroup@fawiki Ladsgroup@labswiki Ladsgroup@testwiki Ladypine@hewiki Lankiveil@enwiki Lanwi1@zhwiki Lar@alswiki Lar@anwiki Lar@arwiki Lar@bgwiki Lar@commonswiki Lar@dewiki Lar@enwiki Lar@enwikiquote Lar@enwikisource Lar@enwikiversity Lar@fawiki Lar@frwiki Lar@hywiki Lar@kywiktionary Lar@metawiki Lar@mswiki Lar@nlwiki Lar@nlwikiquote Lar@plwiki Lar@rowiki Lar@scowiki Lar@simplewiki Lar@simplewiktionary Lar@thwiki Lar@tiwiki Lar@trwiki Lar@ukwiki Lar@viwiki Lar@zhwiki Le chat perché@frwiki Lechatjaune@ptwiki Leinad pl@rowiki Leinad@metawiki Leinad@plwiki Lewisiscrazy@frwiki Lijealso@ptwiki Linedwell@frwiki Linedwell@loginwiki Liso@skwiki Little Sunshine@ptwiki Lofty abyss@abwiki Lofty abyss@acewiki Lofty abyss@astwiki Lofty abyss@bclwiki Lofty abyss@betawikiversity Lofty abyss@bnwiki Lofty abyss@cawiktionary Lofty abyss@chrwiki Lofty abyss@crwiki Lofty abyss@cswikibooks Lofty abyss@cswiktionary Lofty abyss@dawikisource Lofty abyss@dawiktionary Lofty abyss@dewikibooks Lofty abyss@dewikinews Lofty abyss@dewiktionary Lofty abyss@eewiki Lofty abyss@enwikibooks Lofty abyss@enwikiversity Lofty abyss@enwikivoyage Lofty abyss@etwiki Lofty abyss@fawiki Lofty abyss@glwiki Lofty abyss@huwiktionary Lofty abyss@idwikibooks Lofty abyss@idwikiquote Lofty abyss@idwikisource Lofty abyss@idwiktionary Lofty abyss@iewiki Lofty abyss@ilowiki Lofty abyss@incubatorwiki Lofty abyss@itwikisource Lofty abyss@itwikisource Lofty abyss@jvwiki Lofty abyss@kmwiki Lofty abyss@kowikiquote Lofty abyss@lawiki Lofty abyss@lbewiki Lofty abyss@lgwiki Lofty abyss@lmowiki Lofty abyss@lowiki Lofty abyss@lvwiki Lofty abyss@mediawikiwiki Lofty abyss@metawiki Lofty abyss@mnwiki Lofty abyss@mswiki Lofty abyss@ndswiki Lofty abyss@nlwikibooks Lofty abyss@nlwikiquote Lofty abyss@nlwiktionary Lofty abyss@nowiki Lofty abyss@nycwikimedia Lofty abyss@nywiki Lofty abyss@omwiki Lofty abyss@outreachwiki Lofty abyss@papwiki Lofty abyss@pflwiki Lofty abyss@pihwiki Lofty abyss@plwikiquote Lofty abyss@plwikisource Lofty abyss@ptwikibooks Lofty abyss@ptwikinews Lofty abyss@ptwikiquote Lofty abyss@ptwikiversity Lofty abyss@ptwikivoyage Lofty abyss@ptwiktionary Lofty abyss@quwiki Lofty abyss@simplewiki Lofty abyss@simplewiktionary Lofty abyss@sourceswiki Lofty abyss@specieswiki Lofty abyss@suwiki Lofty abyss@svwikinews Lofty abyss@svwikiquote Lofty abyss@swwiki Lofty abyss@tiwiktionary Lofty abyss@trwikisource Lofty abyss@vewiki Lofty abyss@warwiki Lofty abyss@wikidatawiki Lofty abyss@wuuwiki Lofty abyss@yiwiki Lofty abyss@yowiki Lofty abyss@zh_classicalwiki Lord Mota@ptwiki Luk@enwiki Luna Santin@enwiki Lusitana@ptwiki Lusum@itwiki Lymantria@commonswiki Lymantria@wikidatawiki Lzur@plwiki M7@kawiki M7@loginwiki M7@metawiki M7@simplewiki MAna (WMF)@testwiki MBisanz@enwiki MBq@dewiki MF-Warburg@loginwiki MFischer (WMF)@enwiki MGA73@dawiki MGodwin@enwiki MPelletier (WMF)@enwiki MPelletier (WMF)@wikidatawiki MPinchuk (WMF) (usurped)@mediawikiwiki MPostoronca-WMF@testwiki MSzabo-WMF@testwiki MZaplotnik@slwiki Mackensen@enwiki Madaki@itwiki Magister Mathematicae@akwiki Magister Mathematicae@alswiki Magister Mathematicae@angwiki Magister Mathematicae@arwiki Magister Mathematicae@aswiki Magister Mathematicae@azwiktionary Magister Mathematicae@barwiki Magister Mathematicae@bawiki Magister Mathematicae@bpywiki Magister Mathematicae@bugwiki Magister Mathematicae@cawiki Magister Mathematicae@chwiki Magister Mathematicae@commonswiki Magister Mathematicae@cowiki Magister Mathematicae@cswiki Magister Mathematicae@enwiki Magister Mathematicae@enwikinews Magister Mathematicae@eswiki Magister Mathematicae@eswikibooks Magister Mathematicae@eswikinews Magister Mathematicae@eswikiquote Magister Mathematicae@eswikisource Magister Mathematicae@eswikiversity Magister Mathematicae@fawiki Magister Mathematicae@ffwiki Magister Mathematicae@fiwikiquote Magister Mathematicae@fjwiki Magister Mathematicae@gawiktionary Magister Mathematicae@gdwiki Magister Mathematicae@itwikibooks Magister Mathematicae@kabwiki Magister Mathematicae@kowiki Magister Mathematicae@kwwiktionary Magister Mathematicae@metawiki Magister Mathematicae@nahwiki Magister Mathematicae@pihwiki Magister Mathematicae@ptwiki Magister Mathematicae@quwiki Magister Mathematicae@simplewiki Magister Mathematicae@simplewiktionary Magister Mathematicae@specieswiki Magister Mathematicae@stwiki Magister Mathematicae@tswiki Magister Mathematicae@ugwiki Magister Mathematicae@uzwiki Magister Mathematicae@zawiki MagnusA@svwiki Magog the Ogre@commonswiki Mailer diablo@enwiki Majorly@simplewiki Malatinszky@huwiki Marc Mongenet@frwiki MarcGarver@astwiki MarcGarver@azwikibooks MarcGarver@bxrwiki MarcGarver@cewiki MarcGarver@chrwiktionary MarcGarver@crwiki MarcGarver@cswikibooks MarcGarver@cswikiquote MarcGarver@enwikibooks MarcGarver@enwikiquote MarcGarver@enwikiversity MarcGarver@enwikivoyage MarcGarver@eswikiquote MarcGarver@fawiki MarcGarver@hawiki MarcGarver@hewikivoyage MarcGarver@hiwiki MarcGarver@idwikiquote MarcGarver@idwikisource MarcGarver@incubatorwiki MarcGarver@kabwiki MarcGarver@loginwiki MarcGarver@ltwiktionary MarcGarver@mediawikiwiki MarcGarver@mswiki MarcGarver@nlwikimedia MarcGarver@nlwikivoyage MarcGarver@nowiki MarcGarver@piwiki MarcGarver@ptwikivoyage MarcGarver@scowiki MarcGarver@slwiki MarcGarver@svwikivoyage MarcGarver@wuuwiki MarcGarver@zhwiki Marcelo Victor@ptwiki MarcoAurelio@astwiki MarcoAurelio@barwiki MarcoAurelio@chrwiki MarcoAurelio@eewiki MarcoAurelio@enwiki MarcoAurelio@eswikiquote MarcoAurelio@euwiki MarcoAurelio@glwiki MarcoAurelio@loginwiki MarcoAurelio@mediawikiwiki MarcoAurelio@metawiki MarcoAurelio@nowiki MarcoAurelio@ptwiki MarcoAurelio@ptwikiquote MarcoAurelio@sourceswiki MarcoAurelio@testwiki MarcoAurelio@zhwiki Mardetanha@commonswiki Mardetanha@enwiki Mardetanha@loginwiki Mardetanha@metawiki Marine-Blue@jawiki Mark Bergsma@enwiki Mark Bergsma@nlwiki Markov@frwiki Martin H.@commonswiki Martin Urbanec (WMF)@cswiki Martin Urbanec (WMF)@cswikiversity Martin Urbanec (WMF)@loginwiki Martin Urbanec (WMF)@metawiki Martin Urbanec (WMF)@testwiki Martin Urbanec@cswiki Martin Urbanec@enwiki Martin Urbanec@loginwiki Martin Urbanec@metawiki Martin Urbanec@testwiki Masti@loginwiki Masti@plwiki Matanya@enwiki Matanya@hewiki Matanya@testwiki Materialscientist@enwiki Mathis B@frwiki Mathonius@loginwiki Matiia@loginwiki MatthiasGor@plwiki Maurilbert@frwiki MaxSem@bgwiki MaxSem@elwiki MaxSem@enwiki MaxSem@enwikiquote MaxSem@enwikisource MaxSem@fowiktionary MaxSem@hrwiki MaxSem@metawiki MaxSem@nowiki MaxSem@nowikibooks MaxSem@plwiki MaxSem@shwiktionary MaxSem@simplewiki MaxSem@skwiki MaxSem@srwiki MaxSem@stwiki MaxSem@svwiki MaxSem@ukwiki MaxSem@yiwiki MaxSem@zhwiki Maxim@enwiki Mdennis (WMF)@commonswiki Mdennis (WMF)@dewiki Mdennis (WMF)@enwiki Mdennis (WMF)@enwikinews Mdennis (WMF)@enwikiquote Mdennis (WMF)@enwikisource Mdennis (WMF)@frwiki Mdennis (WMF)@frwiktionary Mdennis (WMF)@loginwiki Mdennis (WMF)@metawiki Mdennis (WMF)@simplewiki Mdennis (WMF)@svwiki MdsShakil@loginwiki MdsShakil@test2wiki MdsShakil@testwiki Melos@itwiki Melos@loginwiki Melos@sswiki Meno25@arwiki Mentisock@wikidatawiki Mercy@metawiki Meursault2004@idwiki Mido@arwiki Midom@enwiki Midom@metawiki Mike V@enwiki Mike.lifeguard@commonswiki Mike.lifeguard@enwikibooks Mike.lifeguard@metawiki Mike.lifeguard@pmswiki Mike.lifeguard@rmywiki Mike.lifeguard@tywiki MikkoM@fiwiki Millennium bug@ptwiki Millosh@azwiki Millosh@bswiki Millosh@cswiki Millosh@dewiktionary Millosh@enwiki Millosh@fawiki Millosh@gdwiktionary Millosh@hrwiki Millosh@kowiki Millosh@metawiki Millosh@pmswiki Millosh@ruwiki Millosh@ruwikisource Millosh@sqwiki Millosh@trwiki Millosh@zhwiki Minderbinder@dewiki Minorax@metawiki Mkdw@enwiki Mmenal@frwiki Moneytrees@enwiki Montgomery@eswiki Mormegil@cswiki Morven@enwiki MrJaroslavik@loginwiki MrJaroslavik@testwiki Msz2001@plwiki Mtarch11@itwiki Mtarch11@metawiki MuZemike@enwiki MusikAnimal@enwiki MusikAnimal@loginwiki Mwpnl@trwiki Mxn@viwiki Mykola7@loginwiki Mykola7@ukwiki Mys 721tx@zhwiki Mz7@enwiki NForrester (WMF)@bewiki NForrester (WMF)@bgwiki NForrester (WMF)@commonswiki NForrester (WMF)@enwiki NForrester (WMF)@eswiki NForrester (WMF)@metawiki NForrester (WMF)@ruwiki NForrester (WMF)@shwiki NForrester (WMF)@srwiki NForrester (WMF)@wikidatawiki NForrester (WMF)@zhwiki NKohli (WMF)@testwiki NNair (WMF)@enwiki NahidSultan (WMF)@arwiki NahidSultan (WMF)@arwikinews NahidSultan (WMF)@bnwiki NahidSultan (WMF)@bnwiktionary NahidSultan (WMF)@cawiki NahidSultan (WMF)@commonswiki NahidSultan (WMF)@enwiki NahidSultan (WMF)@enwikinews NahidSultan (WMF)@enwikiquote NahidSultan (WMF)@eswiki NahidSultan (WMF)@ffwiki NahidSultan (WMF)@foundationwiki NahidSultan (WMF)@frwiki NahidSultan (WMF)@hewiki NahidSultan (WMF)@jawiki NahidSultan (WMF)@jawiktionary NahidSultan (WMF)@knwiki NahidSultan (WMF)@kowiki NahidSultan (WMF)@loginwiki NahidSultan (WMF)@mediawikiwiki NahidSultan (WMF)@metawiki NahidSultan (WMF)@miwiki NahidSultan (WMF)@mkwiki NahidSultan (WMF)@mrwiki NahidSultan (WMF)@mswiki NahidSultan (WMF)@ptwiki NahidSultan (WMF)@rowiki NahidSultan (WMF)@ruwiki NahidSultan (WMF)@simplewiki NahidSultan (WMF)@specieswiki NahidSultan (WMF)@srwiki NahidSultan (WMF)@tawiki NahidSultan (WMF)@ukwiki NahidSultan (WMF)@uzwiki NahidSultan (WMF)@wikidatawiki NahidSultan (WMF)@zhwiki NahidSultan@bnwiki NahidSultan@loginwiki Nakor@frwiki Nataev@uzwiki NativeForeigner@enwiki Natuur12@nlwiki Nedops@plwiki Nelson Teixeira@ptwiki Nettadi@hewiki Neukoln@hewiki Newyorkbrad@enwiki Nick1915@bat_smgwiki Nick1915@elwiki Nick1915@fawiki Nick1915@itwikibooks Nick1915@itwiktionary Nick1915@kowiki Nick1915@kwwiki Nick1915@lmowiki Nick1915@metawiki Nick1915@ptwiki Nick1915@scnwiki Nick1915@trwiki Nick1915@zhwiki NickK@ukwiki NinjaRobotPirate@enwiki Nishkid64@enwiki NoFWDaddress@frwiki Nohirara@idwiki Noumenon@trwiki NuclearWarfare@enwiki Nuno Tavares@ptwiki Nux@plwiki Nyenyec@huwiki OJJ@cswiki Octahedron80@thwiki OhanaUnited@enwikivoyage OneLittleMouse@ruwiki OnlyJonny@ptwiki Opabinia regalis@enwiki Operator873@loginwiki Operator873@simplewiki Ori@hewiki Oscar@commonswiki Oscar@dewiki Oscar@enwiki Oscar@frwiki Oscar@metawiki Oscar@nlwiki Oscar@nlwikibooks Oscarami@eswiki Oshwah@enwiki PEarley (WMF)@enwiki PSaxena (WMF)@mediawikiwiki PSaxena (WMF)@testwiki Paginazero@commonswiki Paginazero@enwiki Paginazero@enwikiquote Paginazero@itwiki Paginazero@itwikibooks Paginazero@ruwiki Paginazero@yiwiki Pallerti@huwiki Palnatoke@dawiki Pap3rinik@itwiki Pathoschild@commonswiki Pathoschild@cywiki Pathoschild@elwiki Pathoschild@enwiki Pathoschild@enwikinews Pathoschild@enwikiquote Pathoschild@enwikisource Pathoschild@eowiki Pathoschild@eswiki Pathoschild@eswikiquote Pathoschild@etwiki Pathoschild@fawiki Pathoschild@fiwikinews Pathoschild@hewiki Pathoschild@hsbwiki Pathoschild@huwiki Pathoschild@idwiki Pathoschild@incubatorwiki Pathoschild@itwiki Pathoschild@itwikiquote Pathoschild@klwiki Pathoschild@kwwiktionary Pathoschild@lbwiki Pathoschild@metawiki Pathoschild@miwiki Pathoschild@mlwiki Pathoschild@nlwiki Pathoschild@nnwiki Pathoschild@ocwiki Pathoschild@ptwiki Pathoschild@ruwiktionary Pathoschild@simplewiki Pathoschild@siwiki Pathoschild@sqwiki Pathoschild@srwiki Pathoschild@urwiktionary Pathoschild@vewiki Pathoschild@yiwiki Pathoschild@zhwiki Pathoschild@zhwiktionary PatríciaR@ptwiki Penn Station@jawiki Perrak@dewiki Pete Forsyth (WMF)@enwiki PeterSymonds@metawiki Peterdownunder@simplewiki PhilKnight@enwiki Philippe (WMF)@azwiki Philippe (WMF)@commonswiki Philippe (WMF)@dewiki Philippe (WMF)@enwiki Philippe (WMF)@enwikibooks Philippe (WMF)@enwikinews Philippe (WMF)@enwikiquote Philippe (WMF)@enwikisource Philippe (WMF)@enwikiversity Philippe (WMF)@enwikivoyage Philippe (WMF)@eswiki Philippe (WMF)@fawiki Philippe (WMF)@frwiki Philippe (WMF)@frwiktionary Philippe (WMF)@itwiki Philippe (WMF)@loginwiki Philippe (WMF)@mediawikiwiki Philippe (WMF)@metawiki Philippe (WMF)@mgwiktionary Philippe (WMF)@nlwiki Philippe (WMF)@simplewiki Philippe (WMF)@wikidatawiki Philippe (WMF)@xhwiki Philippe (WMF)@zhwiki Philippe@enwiki Piku@frwiki Pmlineditor@loginwiki Polimerek@plwiki Polimerek@plwikimedia Ponyo@enwiki Postrach@cswiki Poudou!@frwiki Pouyana@fawiki Premeditated Chaos@enwiki Primefac@enwiki Prométhée@frwiki Ptjackyll@plwiki Pundit@plwiki PurpleBuffalo@hewiki Putnik@ruwiki Q-bit array@ruwiki Queix@frwiki RLuts@ukwiki RadiX@bhwiki RadiX@cswikisource RadiX@enwikiversity RadiX@hiwiki RadiX@loginwiki RadiX@pswiki RadiX@ptwiki RadiX@ptwikibooks RadiX@ptwikiquote RadiX@svwikiversity Rastrojo@eswiki Raul654@enwiki Rax@dewiki Razimantv@mlwiki Rdsmith4@arwiki Rdsmith4@biwiki Rdsmith4@etwiki Rdsmith4@fawiki Rdsmith4@gawiki Rdsmith4@huwiki Rdsmith4@idwiki Rdsmith4@jawiki Rdsmith4@kowiki Rdsmith4@lmowiki Rdsmith4@lvwiki Rdsmith4@metawiki Rdsmith4@newiktionary Rdsmith4@nrmwiki Rdsmith4@rowiki Rdsmith4@rowiktionary Rdsmith4@scowiki Rdsmith4@trwiki Rdsmith4@zhwiki ReAl@ukwiki Reaper Eternal@enwiki Redux@enwiki Reedy (WMF)@arwiki Reedy (WMF)@enwiki Reedy (WMF)@mediawikiwiki Reedy (WMF)@metawiki Reedy (WMF)@testwiki Reedy@commonswiki Reedy@enwiki Reedy@itwiki Reedy@labswiki Reedy@wikidatawiki Rei-artur@ptwiki Renamed user mou89p43twvqcvm8ut9w3@enwiki Revi C.@betawikiversity Revi C.@dewikiquote Revi C.@enwiki Revi C.@enwikinews Revi C.@enwikivoyage Revi C.@eswikinews Revi C.@kowiktionary Revi C.@loginwiki Revi C.@mediawikiwiki Revi C.@rowiki Revi C.@ruwikinews Revi C.@ruwikiquote Revi C.@smnwiki Revi C.@zh_classicalwiki Revi C.@zhwiki Rexcornot@frwiki Reza1615@fawiki RiazACU@bnwiki Richwales@enwiki Richwales@eswiki RickinBaltimore@enwiki Risker@enwiki Rlevse@enwiki RobLa-WMF@enwiki RobLa-WMF@metawiki RobLa-WMF@plwiki Rodasmith@enwiktionary Roger Davies@enwiki Rojelio@itwiki Romihaitza@simplewiki Romihaitza@yiwiki RoySmith@arwiki RoySmith@enwiki RoySmith@metawiki Rsocol@rowiki Rubin16@ruwiki Ruslik0@loginwiki Ruslik0@nlwikibooks Ruthven@itwiki Ruthven@testwiki Ruy Pugliesi@mediawikiwiki Rxy@loginwiki Rxy@wikidatawiki Ryan Kaldari (WMF)@commonswiki Ryan Kaldari (WMF)@enwiki Ryan Lane (WMF)@labswiki SB Johnny@commonswiki SB Johnny@enwikibooks SB Johnny@enwikiversity SBJohnny@enwikibooks SBassett (WMF)@enwiki SBassett (WMF)@mediawikiwiki SBassett (WMF)@metawiki SBassett (WMF)@testwiki SEPRodrigues@ptwiki SHB2000@enwikivoyage SHB2000@loginwiki SMP@cawiki SNg (WMF)@enwiki SNg (WMF)@metawiki SNg (WMF)@nlwiki SPoore (WMF)@enwiki SPoore (WMF)@hrwiki SPoore (WMF)@itwiki SPoore (WMF)@metawiki SPoore (WMF)@simplewiki SPoore (WMF)@testwiki SQL@enwiki SSandu-WMF@enwiki SSpalding (WMF)@enwiki ST47@enwiki ST47@testwiki Sadrettin@trwiki Sahehco@fawiki Sahehco@testwiki Sakretsu@itwiki Sakretsu@loginwiki Salvio giuliano@enwiki Sam Korn@enwiki Samuel (WMF)@enwiki Samuel (WMF)@eswiki Samuel (WMF)@frwiki Samuel (WMF)@iawiki Samuel (WMF)@metawiki Samuel (WMF)@testwiki Samuel (WMF)@ukwiki Saper@plwiki Savh@itwikibooks Savh@loginwiki Sbisolo@itwiki Schiste@frwiki Schlum@frwiki Schniggendiller@loginwiki ScottishFinnishRadish@enwiki Sdrqaz@enwiki SelfieCity@enwikivoyage Seraphimblade@enwiki Shanel@aawiki Shanel@arwiki Shanel@cswiki Shanel@cswikiquote Shanel@eewiki Shanel@elwikibooks Shanel@enwiki Shanel@enwikiquote Shanel@eswikisource Shanel@fawiki Shanel@frwikinews Shanel@frwiktionary Shanel@igwiki Shanel@itwikinews Shanel@jawikinews Shanel@kowiki Shanel@mlwiki Shanel@ndswiki Shanel@ptwiki Shanel@ptwikinews Shanel@ptwiktionary Shanel@simplewiktionary Shanel@skwiki Shanel@skwiktionary Shanel@tiwiki Shanel@ukwiki Shanel@vecwiki Shanel@yiwiki Shanel@zhwiki Shanmugamp7@enwiki Shanmugamp7@loginwiki Shell Kinney@enwiki Shivanarayana@itwiki Shizhao@betawikiversity Shizhao@commonswiki Shizhao@dewiki Shizhao@enwiki Shizhao@frwiki Shizhao@kowiki Shizhao@kowikisource Shizhao@metawiki Shizhao@ptwikiquote Shizhao@ruwiki Shizhao@viwiki Shizhao@zhwiki Siam2019@thwiki SilkTork@enwiki SilverLocust@enwiki Sir Lestaty de Lioncourt@ptwikinews Sir kiss@hewiki Sir48@commonswiki Sir48@dawiki SirFozzie@enwiki Sjoerddebruin@loginwiki Skenmy@enwikinews Slaporte (WMF)@commonswiki Slaporte (WMF)@enwiki Slaporte (WMF)@ptwiki Smooth O@bswiki Snowdog@itwiki SoWhy@enwiki Sotiale@enwiki Sotiale@kowiki Sotiale@loginwiki Sotiale@metawiki Sotiale@wikidatawiki Spacebirdy@anwiki Spacebirdy@cawiki Spacebirdy@metawiki Spacebirdy@testwiki Spangineer@enwikisource SpeedyGonsales@hrwiki Spicy@enwiki Squasher@dewiki Stanglavine@loginwiki Stanglavine@metawiki Stanglavine@ptwiki Stephen Bain@enwiki Steve Smith@enwiki Stigfinnare@svwiki Strainu@rowiki Stryn@fiwiki Stryn@loginwiki Stwalkerster@enwiki Suisui@jawiki Superpes15@enwiki Superpes15@enwikiquote Superpes15@fawiki Superpes15@itwiki Superpes15@itwikiversity Superpes15@loginwiki Superpes15@metawiki Superspritz@itwiki Surjection@enwiktionary Syp@huwiki Szwedzki@plwiki TBolliger (WMF)@testwiki THargrove (WMF)@plwiki Tarawneh@arwiki Taweetham@thwiki Tbone@fiwiki Tegel@enwiki Tegel@eswiktionary Tegel@loginwiki Tegel@metawiki Tegel@svwiki Teles@enwiki Teles@loginwiki Teles@metawiki Teles@ptwiki Tfinc@enwiki Tfinc@metawiki Tgr (WMF)@commonswiki Tgr (WMF)@enwiki Tgr (WMF)@loginwiki Tgr (WMF)@metawiki Tgr (WMF)@testwiki Tgr (WMF)@trwiki Tgr@huwiki Thatcher@enwiki The Epopt@enwiki The Rambling Man@simplewiki The Squirrel Conspiracy@commonswiki TheDaveRoss@enwiktionary TheStriker@hewiki Theghaz@dewiki Theleekycauldron@enwiki Thenub314@enwikibooks TheresNoTime@enwiki TheresNoTime@loginwiki TheresNoTime@simplewiki Thogo@afwiki Thogo@alswiki Thogo@arzwiki Thogo@azwiktionary Thogo@barwiki Thogo@bat_smgwiki Thogo@bawiki Thogo@bgwiki Thogo@biwiki Thogo@brwiki Thogo@diqwiki Thogo@eewiki Thogo@elwiktionary Thogo@emlwiki Thogo@enwikisource Thogo@enwikiversity Thogo@enwiktionary Thogo@eowiki Thogo@eswiktionary Thogo@euwiki Thogo@euwiktionary Thogo@fawiki Thogo@fowiktionary Thogo@frwikinews Thogo@frwikiquote Thogo@fywiki Thogo@gawiki Thogo@gdwiktionary Thogo@gvwiktionary Thogo@hiwiki Thogo@hrwiki Thogo@huwiki Thogo@hywiki Thogo@itwikiquote Thogo@itwiktionary Thogo@iuwiki Thogo@jawikinews Thogo@jawikisource Thogo@jawiktionary Thogo@kmwiki Thogo@kowiki Thogo@liwiki Thogo@mediawikiwiki Thogo@miwiki Thogo@ndswiki Thogo@nnwiki Thogo@nowiki Thogo@pamwiki Thogo@ptwikinews Thogo@quwiki Thogo@rowiki Thogo@simplewiktionary Thogo@siwiki Thogo@sourceswiki Thogo@sowiki Thogo@specieswiki Thogo@stqwiki Thogo@vecwiki Thogo@viwiki Thogo@vowiki Thogo@wawiki Thogo@xhwiki Thogo@yiwiki Thogo@zhwiki Thryduulf@enwiki Tim Starling (WMF)@dewiki Tim Starling (WMF)@enwiki Tim Starling (WMF)@mediawikiwiki Tim Starling (WMF)@ruwiki Tim Starling@enwiki Tim Starling@labswiki Tim Starling@metawiki Tim Starling@testwiki Timekeepertmk@thwiki Timotheus Canens@enwiki Tinz@dewiki Tiptoety@commonswiki Tiptoety@enwiki Tiptoety@metawiki Titore@itwiki Tks4Fish@enwiki Tks4Fish@loginwiki Tks4Fish@ptwiki Tnxman307@enwiki ToBeFree@enwiki Tom Morris@enwikinews Tomer T@hewiki TonyBallioni@enwiki Toto Azéro@frwiki Triglav@jawiki Trijnstel@commonswiki Trijnstel@enwiki Trijnstel@loginwiki Trijnstel@metawiki TunnelESON@enwikiquote TunnelESON@fawiki TunnelESON@frwiktionary TunnelESON@jawikibooks TunnelESON@jawiktionary TunnelESON@kowiki TunnelESON@kowikisource TunnelESON@metawiki TunnelESON@mlwiki TunnelESON@ptwiki TunnelESON@ruwiki TunnelESON@siwiki TunnelESON@thwiki TunnelESON@ukwiki TunnelESON@viwiki TunnelESON@zhwiki Tznkai@enwiki Ugur Basak@trwiki Uncitoyen@trwiki UninvitedCompany@enwiki Ustad abu gosok@idwiki VIGNERON@loginwiki VWalters-WMF@enwiki VWalters-WMF@testwiki Vanamonde93@enwiki Vanished user 5zariu3jisj0j4irj@enwiki Vassyana@enwiki Vermont@loginwiki Vermont@metawiki Vermont@simplewiki Versageek@enwiki Versageek@enwiktionary VictorAnyakin@ukwiki Vigorous action@jawiki Vincent Vega@trwiki Viniciusmc@ptwiki Vituzzu@enwiki Vituzzu@eswikiquote Vituzzu@itwiki Vituzzu@metawiki Vituzzu@ptwikibooks Vlad@rowiki Vodomar@hrwiki W.CC@jawiki WBrown (WMF)@test2wiki WBrown (WMF)@testcommonswiki WBrown (WMF)@testwiki WMFOffice@arwiki WMFOffice@commonswiki WMFOffice@enwiki WMFOffice@fawiki WMFOffice@frwiki WMFOffice@loginwiki WMFOffice@mediawikiwiki WMFOffice@metawiki WMFOffice@ruwiki WMFOffice@wikidatawiki Wagino 20100516@idwiki Walter@brwiki Walter@nlwiki Walter@nlwikibooks Walter@testwiki WarX@plwiki Wegge@dawiki Werdna@enwiki Werdna@enwikisource Werdna@mediawikiwiki Whiteknight@enwikibooks Wiki13@loginwiki Wikimol@cswiki Wikitanvir@bnwiki WilliamH@enwiki Wim b@loginwiki Wind@ruwiki Wizardman@enwiki Wojciech Pędzich@betawikiversity Wojciech Pędzich@commonswiki Wojciech Pędzich@dewiktionary Wojciech Pędzich@eewiki Wojciech Pędzich@enwikiquote Wojciech Pędzich@enwikiversity Wojciech Pędzich@fawiki Wojciech Pędzich@fiwikibooks Wojciech Pędzich@itwikinews Wojciech Pędzich@kowiki Wojciech Pędzich@ltwiktionary Wojciech Pędzich@nlwikimedia Wojciech Pędzich@nowiki Wojciech Pędzich@outreachwiki Wojciech Pędzich@plwiki Wojciech Pędzich@plwikibooks Wojciech Pędzich@plwikimedia Wojciech Pędzich@plwikinews Wojciech Pędzich@plwikiquote Wojciech Pędzich@plwiktionary Wojciech Pędzich@rowiki Wojciech Pędzich@simplewiktionary Wojciech Pędzich@szlwiki Wojciech Pędzich@ukwiki Wojciech Pędzich@xhwiki Worm That Turned@enwiki Wugapodes@enwiki Wulfson@ruwiki XXBlackburnXx@enwiki XXBlackburnXx@loginwiki XXBlackburnXx@nlwiki Xania@enwikibooks Xaosflux@loginwiki Xaosflux@metawiki Xeno@enwiki Y-dash@jawiki Yahya@loginwiki Yamla@enwiki Yann@arwiki Yann@commonswiki Yann@enwikisource Yann@frwikinews Yann@hiwiki Yann@itwikiversity Yann@metawiki Yann@sourceswiki Yann@ukwiki YellowMonkey@enwiki Yunshui@enwiki Z1720@enwiki ZExley (WMF)@enwiki ZSoo (WMF)@enwiki Zabe@enwiki Zabe@mediawikiwiki Zabe@test2wiki Zabe@testcommonswiki Zabe@testwiki Zafer@trwikimedia Zzuuzz@enwiki Érico@ptwiki Боки@srwiki Ле Лой@ruwiki Обрадовић Горан@srwiki Стефанко1982@ukwiki בריאן@hewiki דגש@hewiki דולב@hewiki דניאל ב.@hewiki יונה בנדלאק@hewiki ירון@hewiki מוטי@hewiki מתניה@zhwiki עוזי ו.@hewiki עידו@hewiki עli@hewiki רחל1@hewiki باسم@arwiki جار الله@arwiki صالح@arwiki علاء@arwiki علاء@enwiki علاء@loginwiki علاء@wikidatawiki فيصل@arwiki ميموني@arwiki আফতাবuজ্জামান@bnwiki さかおり@jawiki 柏尾菓子@jawiki 欅@jawiki 海獺@jawiki 이강철@kowiki `; // PASTE YOUR LIST HERE function getDomain(db) { const mapping = { 'metawiki': 'meta.wikimedia.org', 'commonswiki': 'commons.wikimedia.org', 'wikidatawiki': 'www.wikidata.org', 'loginwiki': 'login.wikimedia.org', 'sourceswiki': 'wikisource.org', 'specieswiki': 'species.wikimedia.org', 'incubatorwiki': 'incubator.wikimedia.org', 'foundationwiki': 'foundation.wikimedia.org', 'outreachwiki': 'outreach.wikimedia.org' }; if (mapping[db]) return mapping[db]; if (db.includes('wikimania')) { const year = db.match(/\d+/); return year ? `wikimania${year}.wikimedia.org` : 'wikimania.wikimedia.org'; } const projects = ['wiktionary', 'wikibooks', 'wikiquote', 'wikinews', 'wikiversity', 'wikivoyage']; for (const p of projects) { if (db.endsWith(p)) return `${db.replace(p, '').replace(/_/g, '-')}.${p}.org`; } return `${db.replace('wiki', '').replace(/_/g, '-')}.wikipedia.org`; } function initInterface() { document.title = "GCUS Data Generator"; const $body = $('#mw-content-text'); $body.empty(); $body.append(` <div style="background:white; border:1px solid #a2a9b1; padding:20px; font-family:sans-serif; color:#202122;"> <h1 style="margin-top:0; border-bottom:1px solid #a2a9b1; padding-bottom:10px;">GCUS Data Generator v6.9.4</h1> <p>Generates Lua data for <code>Module:GlobalRoles/Data</code>. <br> <b>Filter:</b> Local CU < 60 days ignored. <b>Format:</b> YYYY-MM-DD. <b>Auto-skip unreachable wikis.</b></p> <div style="display:flex; gap:15px; margin-bottom:20px;"> <div style="flex:1;"> <strong>Input List:</strong> <textarea id="gcusInput" style="width:100%; height:180px; font-family:monospace; font-size:12px; border:1px solid #a2a9b1; margin-top:5px;">${USERS_LIST}</textarea> </div> <div style="flex:1;"> <strong>Progress Log:</strong> <div id="gcusLog" style="height:180px; overflow-y:scroll; background:black; color:#0f0; font-family:monospace; font-size:11px; padding:5px; border-radius:3px; margin-top:5px;">Ready...</div> </div> <div style="width:200px;"> <strong>Failed Wikis:</strong> <textarea id="gcusFailed" readonly style="width:100%; height:180px; font-family:monospace; font-size:11px; background:#fff0f0; border:1px solid #fcc; margin-top:5px; color:#a00;"></textarea> </div> </div> <button id="gcusStart" style="padding:10px 24px; background:#36c; color:white; border:none; cursor:pointer; font-weight:bold;">START SCAN</button> <span id="gcusStatus" style="margin-left:15px; font-weight:bold;"></span> <div id="gcusResultArea" style="display:none; margin-top:20px;"> <strong>Lua Output:</strong><br> <textarea id="gcusOutput" style="width:100%; height:350px; font-family:monospace; font-size:12px; background:#fffbe6; border:1px solid #f2e394; margin-top:5px;"></textarea> </div> </div> `); $('#gcusStart').on('click', runGenerator); } async function runGenerator() { const input = $('#gcusInput').val(); const $log = $('#gcusLog'); const $output = $('#gcusOutput'); const $status = $('#gcusStatus'); const $btn = $('#gcusStart'); const $failed = $('#gcusFailed'); $btn.prop('disabled', true).css('background', '#a2a9b1').text('SCANNING...'); $log.empty(); $failed.val(''); $('#gcusResultArea').show(); const log = (msg) => { $log.append($('<div>').text(`[${new Date().toLocaleTimeString()}] ${msg}`)); $log.scrollTop($log[0].scrollHeight); }; const sleep = ms => new Promise(r => setTimeout(r, ms)); async function safeFetch(url, wikiName, attempts = 0) { await sleep(800); const fullUrl = url + "&origin=*&contact=User:MrJaroslavik"; try { const response = await fetch(fullUrl); if (response.status === 429) { const retry = response.headers.get('Retry-After') || 60; log(`Rate limit! Waiting ${retry}s...`); await sleep(retry * 1000); return safeFetch(url, wikiName, attempts); } if (!response.ok) throw new Error(`HTTP ${response.status}`); return await response.json(); } catch (e) { if (attempts < 2) { log(`Retrying ${wikiName} (${attempts + 1}/3)...`); await sleep(2000); return safeFetch(url, wikiName, attempts + 1); } log(`!! Skipped: ${wikiName} (${e.message})`); const currentFailed = $failed.val(); if (!currentFailed.includes(wikiName)) { $failed.val(currentFailed + wikiName + '\n'); } return null; } } function checkChange(logEntry, roleName) { const p = logEntry.params; if (!p) return null; const r = roleName.toLowerCase(); const normalize = (v) => Array.isArray(v) ? v.join(',').toLowerCase() : String(v || "").toLowerCase(); const oldStr = normalize(p.oldGroups || p.oldgroups || p.remove || p[0]); const newStr = normalize(p.newGroups || p.newgroups || p.add || p[1]); const hasRole = (s) => new RegExp('(^|,|\\s)' + r + '($|,|\\s)').test(s); if (!hasRole(oldStr) && hasRole(newStr)) return "added"; if (hasRole(oldStr) && !hasRole(newStr)) return "removed"; return null; } function getDaysDiff(dateFrom, dateTo) { if (dateTo === 'present') return 999; const d1 = new Date(dateFrom); const d2 = new Date(dateTo); return Math.floor((d2 - d1) / (1000 * 60 * 60 * 24)); } const localArray = input.split('\n').map(s => s.trim()).filter(s => s.length > 0); const globalArray = [...new Set(localArray.map(s => { const parts = s.split('@'); parts.pop(); return parts.join('@'); }))]; let rawData = {}; log(`Started. Processing ${globalArray.length} unique users.`); // 1. GLOBAL ROLES for (let i = 0; i < globalArray.length; i++) { const name = globalArray[i]; const gDb = 'global'; $status.text(`Global: ${i+1}/${globalArray.length} (${name})`); let continueToken = ''; while (continueToken !== undefined) { const data = await safeFetch(`https://meta.wikimedia.org/w/api.php?action=query&list=logevents&letype=gblrights&letitle=User:${encodeURIComponent(name)}&lelimit=max${continueToken}&format=json`, 'metawiki'); if (!data) break; const logs = (data.query && data.query.logevents ? data.query.logevents : []).reverse(); logs.forEach(l => { const date = l.timestamp.substring(0, 10); ['steward', 'staff', 'ombuds'].forEach(role => { const change = checkChange(l, role); if (change === "added") { if (!rawData[name]) rawData[name] = []; rawData[name].push({ role, db: gDb, from: date, to: 'present' }); } else if (change === "removed") { const entries = rawData[name] || []; const last = entries.filter(e => e.role === role && e.to === 'present').pop(); if (last) last.to = date; } }); }); continueToken = data.continue ? `&lecontinue=${data.continue.lecontinue}` : undefined; } } // 2. LOCAL ROLES for (let i = 0; i < localArray.length; i++) { const parts = localArray[i].split('@'); const lDb = parts.pop(); const name = parts.join('@'); const userEntries = rawData[name] || []; if (lDb === 'loginwiki' && userEntries.some(e => e.db === 'global' && e.role === 'steward')) continue; $status.text(`Local: ${i+1}/${localArray.length} (${name}@${lDb})`); let continueToken = ''; const domain = getDomain(lDb); try { while (continueToken !== undefined) { const data = await safeFetch(`https://${domain}/w/api.php?action=query&list=logevents&letype=rights&letitle=User:${encodeURIComponent(name)}&lelimit=max${continueToken}&format=json`, lDb); if (!data) break; const logs = (data.query && data.query.logevents ? data.query.logevents : []).reverse(); logs.forEach(l => { const date = l.timestamp.substring(0, 10); const change = checkChange(l, 'checkuser'); if (change === "added") { if (!rawData[name]) rawData[name] = []; rawData[name].push({ role: 'cu', db: lDb, from: date, to: 'present' }); } else if (change === "removed") { const entries = rawData[name] || []; const last = entries.filter(e => e.role === 'cu' && e.db === lDb && e.to === 'present').pop(); if (last) last.to = date; } }); continueToken = data.continue ? `&lecontinue=${data.continue.lecontinue}` : undefined; } } catch(e) { log(`Error ${name}@${lDb}: ${e.message}`); } } // 3. GENERATE LUA let lua = "return {\n users = {\n"; const sortedUsers = Object.keys(rawData).sort(); for (const user of sortedUsers) { const filteredEntries = rawData[user].filter(e => { if (e.db === 'global') return true; if (e.to === 'present') return true; return getDaysDiff(e.from, e.to) >= 60; }); if (filteredEntries.length > 0) { lua += ` ["${user}"] = {\n`; filteredEntries.forEach(e => { lua += ` { role = "${e.role}", db = "${e.db}", from = "${e.from}", to = "${e.to}" },\n`; }); lua += ` },\n`; } } lua += " }\n}"; $output.val(lua); $status.text("FINISHED!"); log("Scan complete. Unreachable projects were reported."); $btn.prop('disabled', false).css('background', '#36c').text('RUN AGAIN'); } $(initInterface); })(); /* </nowiki> */ 2n23wrmmaid6bql56v8xeb9z6dg7tzy 739778 739777 2026-04-29T14:51:00Z MrJaroslavik 44012 + 739778 javascript text/javascript /* <nowiki> */ /** * GCUS Data Generator /* */ (function() { if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || mw.config.get('wgTitle').split('/').pop() !== 'GCUS-Data') { return; } const USERS_LIST = `.anaconda@kvwiki .anaconda@tiwiki .mau.@itwiki 0x010C@frwiki 1234qwer1234qwer4@loginwiki 1997kB@wikidatawiki 3(MG)²@frwiki A09@loginwiki A09@slwiki ABX@plwiki AJones (WMF)@enwiki AJones (WMF)@metawiki AKarani (WMF)@enwiki AKoval (WMF)@enwiki ATripathi-WMF@enwiki ATripathi-WMF@eswiki Aaron Schulz@enwiki Abisys@itwiki Acagastya@commonswiki Acagastya@enwikinews Adailton@ptwiki AdiJapan@rowiki Adler.fa@fawiki Adrian@skwiki Adrignola@enwikibooks Agony@fiwiki Ajraddatz@commonswiki Ajraddatz@enwiki Ajraddatz@eswiki Ajraddatz@loginwiki Ajraddatz@metawiki Akeron@frwiki Akoopal@nlwiki Alain r@frwiki Alan@commonswiki Alan@eswiki Alan@metawiki Albertoleoncio@loginwiki Albertoleoncio@ptwiki Aldnonymous@idwiki Alex Shih@enwiki Alexander Misel@zhwiki Alexanderps@ptwiki Alexanderps@ptwikinews Alhen@eswiki Alison@enwiki Alnokta@arwiki Alphax@commonswiki Alraunenstern@dewiki Amalthea@enwiki AmandaNP@enwiki AmandaNP@hrwiki AmandaNP@itwiki AmandaNP@loginwiki AmandaNP@wikidatawiki Andre Engels@acewiki Andre Engels@anwiki Andre Engels@barwiki Andre Engels@bgwiki Andre Engels@bswiki Andre Engels@dewiki Andre Engels@eswiktionary Andre Engels@fawiki Andre Engels@fawikiquote Andre Engels@kowiki Andre Engels@lvwiki Andre Engels@mswiki Andre Engels@nlwiki Andre Engels@ptwiki Andre Engels@ptwiktionary Andre Engels@sourceswiki Andre Engels@specieswiki Andre Engels@testwiki Andre Engels@urwiki Andre Engels@viwiki Andre Engels@zeawiki Andre Engels@zhwiki Andrei Stroe@rowiki Andrej Šalov@hrwiki Andriy.v@ukwiki Annabel@nlwiki Anr@fiwiki Anthere@enwiki Anthere@metawiki AntiCompositeNumber@loginwiki Antime@arwiki Aoidh@enwiki Aoineko@frwiki Aphaia@enwikiquote AramilFeraxa@loginwiki AramilFeraxa@metawiki AramilFeraxa@plwiki Aratal@frwiki Arcticocean@enwiki Arcticocean@hrwiki Arcticocean@kowiki Arcticocean@ptwiki Arcticocean@ukwiki Argo Navis@hrwiki Ash Crow@frwiki Asilvering@enwiki Ask21@itwiki Ausir@plwiki Avocato@arwiki Avraham@enwiki Avraham@loginwiki Azafred@enwiki B20180@thwiki BDD@enwiki BRPever@loginwiki BRPever@simplewiki BRPever@wikidatawiki Bahamut0013@enwiki Barak a@hewiki Barcex@eswiki Barkeep49@enwiki Barras@enwiki Barras@loginwiki Barras@metawiki Barras@simplewiki Base@loginwiki Bastique@azwiki Bastique@commonswiki Bastique@crwiki Bastique@enwiki Bastique@enwikibooks Bastique@enwikiquote Bastique@enwikiversity Bastique@eswiki Bastique@nowiki Bastique@plwiki Bastique@ruwiki Bastique@simplewiki Bastique@stwiki Bastique@svwiki Bastique@testwiki Bastique@trwiki Bastique@wikimania2008wiki Bastique@yiwiki Bastique@zhwiki Bbb23@enwiki Bdk@dewiki Beeblebrox@enwiki Bellcricket@jawiki Bencmq@zhwiki Benevolent Otter@plwiki Bennylin@idwiki Benoît Prieur@frwiki Berean Hunter@enwiki Beria@ptwiki Bernard@eswiki BetoCG@eswiki Billinghurst@enwikisource Billinghurst@eswikivoyage Billinghurst@loginwiki Billinghurst@metawiki Billinghurst@plwikinews Billinghurst@slwiki Bináris2@huwiki Bináris@huwiki Biologo32@ptwiki Blablubbs@enwiki BlackBeast@eswiki Borgx@idwiki Bradv@enwiki Brandon@enwiki BraneJ@srwiki Brian McNeil@enwikinews Brian@enwikinews Brooke Vibber@dewiki Brooke Vibber@enwiki Brooke Vibber@frwiki Brooke Vibber@mediawikiwiki Brooke Vibber@testwiki Bryan@commonswiki BryanDavis@labswiki Bsadowski1@loginwiki Bsadowski1@metawiki Bsadowski1@simplewiki CAshraf (WMF)@enwiki CLo (WMF)@testwiki CMaslak (WMF)@enwiki CMaslak (WMF)@fawiki CMaslak (WMF)@srwiki CMaslak (WMF)@uzwiki CPettet (WMF)@labswiki CSteigenberger (WMF)@cawiki CSteigenberger (WMF)@enwiki CSteigenberger (WMF)@jawiki CSteigenberger (WMF)@metawiki CSteipp (WMF)@ptwiktionary Cabayi@enwiki Caesar@svwiki Callanecc@enwiki CaptainEek@enwiki Carkuni@jawiki Cartman02au@metawiki Cary Bass@arwiki Cary Bass@aswiki Cary Bass@commonswiki Cary Bass@enwiki Cary Bass@enwikiquote Cary Bass@enwikiversity Cary Bass@frwiki Cary Bass@hiwiki Cary Bass@itwiki Cary Bass@metawiki Cary Bass@ptwiki Casliber@enwiki Cassiopeia sweet@jawiki Cato@enwikiquote Cdip150@zhwiki Cgt@dawiki Chaos@arwiki Chase me ladies, I'm the Cavalry@enwiki Chatama@jawiki Christian List@dawiki Christine (WMF)@enwiki Chuck Entz@enwiktionary Cinabrium@eswiki Ciphers@arwiki Cirdan@dewiki Cirt@enwikinews Civvi~itwiki@itwiki Clem23@frwiki Codc@dewiki CodeMonk@ruwiki Coet@cawiki Conde Edmond Dantès@ptwiki Connel MacKenzie@enwiktionary Cool Hand Luke@enwiki Coren@enwiki Coren@labswiki Count Count@dewiki Count Count@loginwiki Courcelles@enwiki Creol@simplewiki Cromium@elwiki Cromium@enwikinews Cromium@loginwiki Cromium@rowiki Cromium@sqwiki Cromium@zhwiki Cruccone@itwiki Csigabi@huwiki Cspurrier@enwikinews Cspurrier@enwikiquote Cspurrier@enwikiversity Cspurrier@fawiki Cspurrier@itwiki Cspurrier@metawiki Cspurrier@ptwiki Cspurrier@simplewiki Cspurrier@ukwiki Cspurrier@zhwiki Céréales Killer@frwiki DBarratt (WMF)@testwiki DGG@enwiki DHN@viwiki DJackson (WMF)@testwiki DMaza (WMF)@testwiki DR@ruwiki DWalden (WMF)@frwiki DWalden (WMF)@testwiki Daedalus@svwiki Daimore@ptwiki Damzow@hewiki Dan Koehl@specieswiki Daniel Quinlan@enwiki Daniel@enwiki Daniuu@arwiki Daniuu@loginwiki Daniuu@metawiki Daniuu@nlwiki Daniuu@testwiki Danmichaelo@nowiki DannyH (WMF)@mediawikiwiki DannyH (WMF)@testwiki DannyS712@testwiki Danu Widjajanto@idwiki Darkoneko@aswiki Darkoneko@brwiki Darkoneko@bugwiki Darkoneko@cswiki Darkoneko@cswikisource Darkoneko@cswiktionary Darkoneko@dawiki Darkoneko@enwiki Darkoneko@enwikiversity Darkoneko@eswiki Darkoneko@fawiki Darkoneko@frwiki Darkoneko@frwikiquote Darkoneko@glwiki Darkoneko@huwiki Darkoneko@jawiktionary Darkoneko@mediawikiwiki Darkoneko@metawiki Darkoneko@mlwiki Darkoneko@newiki Darkoneko@nlwikiquote Darkoneko@ruwiktionary Darkoneko@skwiki Darkoneko@skwikisource Darkoneko@sourceswiki Darkoneko@srwiki Darkoneko@stwiki Darkoneko@testwiki Darkoneko@trwiki Darkoneko@ukwiki Darkoneko@viwiki Darkoneko@warwiki Darkoneko@wawiki Darkoneko@wowiki Darkoneko@yiwiki DatGuy@enwiki Datrio@fawiki David Fuchs@enwiki David Gerard@enwiki David Wadie Fisher-Freberg@idwiki Dbeef@enwiki Dbl2010@azwiki Dbl2010@kowiktionary Dbl2010@testwiki Dbl2010@trwiki Dbl2010@vlswiki Dcastor@svwiki Deineka@ukwiki Demicx@bswiki Der-Wir-Ing@dewiki DerHexer@loginwiki DerHexer@metawiki Derbeth@enwikibooks Deror avi@hewiki Deskana (WMF)@enwiki Deskana (WMF)@mediawikiwiki Deskana@enwiki Dferg@acewiki Dferg@bat_smgwiki Dferg@bewiki Dferg@bgwiki Dferg@brwiki Dferg@cebwiki Dferg@enwikiversity Dferg@eowiki Dferg@extwiki Dferg@fawiki Dferg@frwikiversity Dferg@gdwiki Dferg@glwiktionary Dferg@hawwiki Dferg@hiwiki Dferg@hywiki Dferg@iswiki Dferg@itwikiquote Dferg@jawikiquote Dferg@kawiki Dferg@kowiki Dferg@kshwiki Dferg@lawiki Dferg@lvwiki Dferg@mkwiktionary Dferg@mswiki Dferg@ndswiki Dferg@orwiktionary Dferg@pmswiki Dferg@ptwikibooks Dferg@ptwikisource Dferg@ptwiktionary Dferg@quwiki Dferg@rowiki Dferg@siwiki Dferg@specieswiki Dferg@sqwiki Dferg@swwiki Dferg@tewiki Dferg@ukwiki Dferg@uzwiki Dferg@viwiki Dferg@vowiki Dferg@wuuwiki Dferg@xalwiki Dferg@xhwiki Dferg@zh_classicalwiki Dferg@zh_yuewiki Dferg@zhwikinews Djordjes@srwiki Djsasso@simplewiki Dmcdevit@wikimania2008wiki Dmitry Gerasimov@ruwiki Dmthoth@kowiki DoRD@enwiki Dominic@enwiki Dominic@enwiktionary Doug Weller@enwiki Doğu@commonswiki Doğu@itwiki Dr-Taher@arwiki Dragoniez@jawiki Drahreg01@dewiki Drbug@ruwiki Dreamy Jazz@enwiki Dreamy Jazz@testwiki Drini@aawiki Drini@afwiki Drini@anwiki Drini@astwiki Drini@azwiki Drini@bgwiki Drini@brwiki Drini@cywiki Drini@dawikibooks Drini@dsbwiki Drini@enwikibooks Drini@enwikiquote Drini@enwikisource Drini@enwikiversity Drini@eowiki Drini@eswiki Drini@etwiki Drini@euwiktionary Drini@extwiki Drini@fawiki Drini@fiwiki Drini@fiwikibooks Drini@fiwikinews Drini@fiwikisource Drini@frpwiki Drini@frwikiquote Drini@gawiki Drini@glwiki Drini@hakwiki Drini@hiwiki Drini@idwiki Drini@iewiki Drini@igwiki Drini@incubatorwiki Drini@iswiki Drini@iswikibooks Drini@itwikisource Drini@itwikisource Drini@jvwiki Drini@kmwiki Drini@kowikiquote Drini@lawiki Drini@lbewiki Drini@lgwiki Drini@lmowiki Drini@lowiki Drini@lvwiki Drini@mediawikiwiki Drini@mlwiki Drini@mswiki Drini@ngwiki Drini@nlwikiquote Drini@nowiki Drini@nowikisource Drini@nvwiki Drini@piwiki Drini@rnwiki Drini@rowiki Drini@ruwiki Drini@ruwikinews Drini@ruwikiquote Drini@scnwiktionary Drini@sgwiki Drini@shwiki Drini@skwiki Drini@snwiki Drini@sowiki Drini@sqwiki Drini@srwiki Drini@swwiki Drini@testwiki Drini@tlwiki Drini@towiki Drini@ukwiki Drini@ukwikinews Drini@ukwiktionary Drini@viwiki Drini@wuuwiki Drini@xalwiki Drini@xhwiki Drini@yiwiki Drini@zh_yuewiki Drini@zhwiki Drmies@enwiki Dtom@hrwiki Dungodung@metawiki Dungodung@srwiki Dungodung@testwiki Durifon@frwiki Dyolf77@arwiki Dyolf77@frwiki Dzordzm@srwiki E.coli@hrwiki EMill-WMF@enwiki EPIC@enwiki EPIC@loginwiki EPIC@metawiki EVula@enwikiquote EdJohnston@enwiki Edmenb@eswiki Effeietsanders@dewiktionary Effeietsanders@enwikiquote Effeietsanders@enwikiversity Effeietsanders@fawiki Effeietsanders@kowiki Effeietsanders@kshwiki Effeietsanders@kuwiki Effeietsanders@nowiki Effeietsanders@ptwiki Effeietsanders@testwiki Einsbor@enwiki Einsbor@loginwiki Ejs-80@fiwiki Elcobbola@commonswiki Eldarion@trwiki Elen of the Roads@enwiki Elfix@cywiktionary Elfix@dewikibooks Elfix@frwiki Elfix@idwikiquote Elfix@itwikinews Elfix@loginwiki Elfix@mywiki Elfix@ptwikibooks Elfix@ptwikiquote Elfix@ptwikisource Elian@dewiki Elli@enwiki Ellywa@nlwiki Elmacenderesi@trwiki Elockid@enwiki Elph@arwiki Elton@astwiki Elton@cebwiki Elton@chwiki Elton@incubatorwiki Elton@jawikinews Elton@loginwiki Elton@mediawikiwiki Elton@plwikiquote Elton@ptwikiquote Elton@shwiki Elton@testwiki Elton@wikidatawiki Elton@zhwiktionary Elwood@itwiki Emir Kotromanić@bswiki Emufarmers@enwiki Emufarmers@eswiki Emufarmers@metawiki Emx@bswiki Enterprisey@enwiki Epinheiro@ptwiki Eptalon@simplewiki Erkan Yilmaz@enwikiversity Erwin85@zhwiki Erwin@nlwiki Essjay@enwiki Eta Carinae@ptwiki Euryalus@enwiki Eusebius@commonswiki EvgenyGenkin@ruwiki Ex13@hrwiki FT2@enwiki Faendalimas@specieswiki Faso@jawiki FayssalF@enwiki Felipe da Fonseca@ptwiki Ferret@enwiki Filzstift@dewiki Firefly@enwiki Fitindia@commonswiki FloNight@enwiki Fluff@svwiki FoBe@huwiki Fr33kman@simplewiki Frank@enwiki Fred Bauder@enwiki Freetrashbox@jawiki Fritzpoll@enwiki Gac@itwiki Galahad@eswiki Galahad@fawiki Galahad@trwiki Gamaliel@enwiki GeneralNotability@enwiki Geonuch@thwiki Giraffer@enwiki Girth Summit@enwiki Gmaxwell@commonswiki Gnom@dewiki Goo3@ukwiki GorillaWarfare@enwiki Gpvos@nlwiki Gribeco@frwiki Grin@huwiki Groucho NL@nlwiki Guerillero@enwiki Guillom@arwiki Guillom@cvwiki Guillom@dawiki Guillom@fawiki Guillom@fiwikiquote Guillom@foundationwiki Guillom@frwiki Guillom@frwikinews Guillom@frwikisource Guillom@frwiktionary Guillom@iawiki Guillom@ilowiki Guillom@iowiki Guillom@iswiki Guillom@itwikinews Guillom@kabwiki Guillom@kowiki Guillom@rowiki Guillom@siwiktionary Guillom@trwiki Gutza@rowiki Gvf@itwiki Góngora@cawiki HJ Mitchell@enwiki HVL@ptwiki Ha98574@kowiki HaeB@dewiki HaithamS (WMF)@enwiki HakanIST@loginwiki Hardscarf@nlwiki Harel@hewiki Haros@nowiki Harriv@fiwiki Hasley@barwiki Hasley@loginwiki Hasley@metawiki Hayabusa future@idwiki Hei ber@dewiki Hei ber@enwiki Hephaion@dewiki Herbythyme@commonswiki Herbythyme@enwikibooks Herbythyme@metawiki Herbythyme@nowiki Hersfold@enwiki Hexasoft@frwiki Hidayatsrf@idwiki Hidro@hewiki Hispa@eswiki Hjvannes@nlwiki Hoo man@loginwiki HouseBlaster@enwiki Huji@fawiki Hunyadym@huwiki Hyméros@frwiki INeverCry@commonswiki IRTC1015@kowiki Ilya Voyager@ruwiki IlyaHaykinson@enwikinews Indech@ptwiki Infinite0694@eswiki Infinite0694@jawiki Infinite0694@loginwiki Infinite0694@metawiki Iridescent@enwiki IvanLanin@idwiki Ivanvector@enwiki Izno@enwiki J.delanoy@enwiki JAbrams (WMF)@cebwiki JAbrams (WMF)@commonswiki JAbrams (WMF)@cswiktionary JAbrams (WMF)@enwiki JAbrams (WMF)@eowiki JAbrams (WMF)@frwiki JAbrams (WMF)@jawiki JAbrams (WMF)@metawiki JAbrams (WMF)@ptwiki JAbrams (WMF)@wikidatawiki JCrespo (WMF)@labswiki JEissfeldt (WMF)@dewiki JEissfeldt (WMF)@enwiki JEissfeldt (WMF)@jawiki JJMC89@eswiki JJMC89@loginwiki JSutherland (WMF)@afwiki JSutherland (WMF)@arwiki JSutherland (WMF)@arwikisource JSutherland (WMF)@azwiki JSutherland (WMF)@bgwiki JSutherland (WMF)@commonswiki JSutherland (WMF)@cswiki JSutherland (WMF)@dawiki JSutherland (WMF)@dewiki JSutherland (WMF)@enwiki JSutherland (WMF)@enwikibooks JSutherland (WMF)@enwikiquote JSutherland (WMF)@enwiktionary JSutherland (WMF)@eswiki JSutherland (WMF)@fawiki JSutherland (WMF)@frwiki JSutherland (WMF)@frwikiquote JSutherland (WMF)@frwikiversity JSutherland (WMF)@hewiki JSutherland (WMF)@hrwiki JSutherland (WMF)@huwiki JSutherland (WMF)@hywiki JSutherland (WMF)@idwiki JSutherland (WMF)@itwiki JSutherland (WMF)@jawiki JSutherland (WMF)@kowiki JSutherland (WMF)@loginwiki JSutherland (WMF)@mediawikiwiki JSutherland (WMF)@metawiki JSutherland (WMF)@nlwiki JSutherland (WMF)@nowiki JSutherland (WMF)@plwiki JSutherland (WMF)@ptwiki JSutherland (WMF)@ruwiki JSutherland (WMF)@simplewiki JSutherland (WMF)@svwiki JSutherland (WMF)@testwiki JSutherland (WMF)@ukwiki JSutherland (WMF)@wikidatawiki JSutherland (WMF)@zhwiki JWSchmidt@enwikiversity JWang (WMF)@testwiki Jafeluv@fiwiki Jagro@cswiki Jake Park@eswiki Jalexander-WMF@arwiki Jalexander-WMF@azwiki Jalexander-WMF@bgwiki Jalexander-WMF@cawiki Jalexander-WMF@commonswiki Jalexander-WMF@cswiki Jalexander-WMF@dawiki Jalexander-WMF@dewiki Jalexander-WMF@enwiki Jalexander-WMF@enwikibooks Jalexander-WMF@enwikiquote Jalexander-WMF@enwikisource Jalexander-WMF@enwikiversity Jalexander-WMF@enwikivoyage Jalexander-WMF@enwiktionary Jalexander-WMF@eswiki Jalexander-WMF@eswikivoyage Jalexander-WMF@frwiki Jalexander-WMF@gawiki Jalexander-WMF@hewiki Jalexander-WMF@hewikisource Jalexander-WMF@incubatorwiki Jalexander-WMF@iswiki Jalexander-WMF@itwiki Jalexander-WMF@kowiki Jalexander-WMF@kowikibooks Jalexander-WMF@kowikisource Jalexander-WMF@loginwiki Jalexander-WMF@mediawikiwiki Jalexander-WMF@metawiki Jalexander-WMF@mywiki Jalexander-WMF@nowiki Jalexander-WMF@nowiktionary Jalexander-WMF@plwiki Jalexander-WMF@ptwiki Jalexander-WMF@ruwiki Jalexander-WMF@ruwikibooks Jalexander-WMF@ruwikisource Jalexander-WMF@simplewiki Jalexander-WMF@specieswiki Jalexander-WMF@svwiki Jalexander-WMF@testwiki Jalexander-WMF@wikidatawiki Jalexander-WMF@zhwiki Jalexander@foundationwiki Jalexander@labswiki James LL@loginwiki Jameslwoodward@commonswiki Jamesofur@simplewiki Jamie Tubers@enwiki Janorovic Volkov@idwiki Japiot@nlwiki Jasper Deng@wikidatawiki Jayjg@enwiki Jbribeiro1@ptwiki Jcb@nlwiki Jclemens@enwiki Jdforrester (WMF)@enwiki Jdforrester (WMF)@enwikiquote Jdforrester@enwiki Jean-Christophe BENOIST@frwiki Jeffq@enwikiquote Jimbo Wales@enwiki Jimmy Xu@zhwiki Jmrebes@cawiki Jniemenmaa@fiwiki Joe Roe@enwiki Johannnes89@loginwiki Johannnes89@metawiki John Vandenberg@enwiki John Vandenberg@enwikisource Jon Harald Søby@akwiki Jon Harald Søby@alswiki Jon Harald Søby@cawiki Jon Harald Søby@cswiki Jon Harald Søby@dewiki Jon Harald Søby@enwiki Jon Harald Søby@enwikiquote Jon Harald Søby@fawiki Jon Harald Søby@fiwikibooks Jon Harald Søby@fiwikisource Jon Harald Søby@frwiki Jon Harald Søby@iswiki Jon Harald Søby@itwiki Jon Harald Søby@kowiki Jon Harald Søby@metawiki Jon Harald Søby@nnwiki Jon Harald Søby@nowiki Jon Harald Søby@nowikibooks Jon Harald Søby@plwikibooks Jon Harald Søby@plwikinews Jon Harald Søby@plwiktionary Jon Harald Søby@ruwiki Jon Harald Søby@skwiki Jon Harald Søby@svwiki Jon Harald Søby@svwikinews Jon Harald Søby@testwiki Jon Harald Søby@trwiki Jon Harald Søby@yiwiki Jon Kolbert@loginwiki Joutbis@cawiki Jpgordon@enwiki Jrogers (WMF)@metawiki Julle@svwiki Julle@testwiki Just Sayori@thwiki JusteJuju10@frwiki Jyothis@eswiktionary Jyothis@loginwiki Jyothis@metawiki KColeman-WMF@testwiki KHarlan (WMF)@loginwiki KHarlan (WMF)@testwiki KLevan (WMF)@arwiki KLevan (WMF)@commonswiki KLevan (WMF)@enwiki KLevan (WMF)@hewiki KLevan (WMF)@idwiki KLevan (WMF)@metawiki KMT@jawiki KRLS@cawiki Kaare@dawiki Kal-El~bswiki@bswiki Kalliope (WMF)@azwiki Kalliope (WMF)@commonswiki Kalliope (WMF)@dewiki Kalliope (WMF)@enwiki Kalliope (WMF)@enwikiversity Kalliope (WMF)@enwiktionary Kalliope (WMF)@eswiki Kalliope (WMF)@frwiki Kalliope (WMF)@frwiktionary Kalliope (WMF)@idwiki Kalliope (WMF)@itwiki Kalliope (WMF)@jawiki Kalliope (WMF)@loginwiki Kalliope (WMF)@metawiki Kalliope (WMF)@outreachwiki Kalliope (WMF)@testwiki Kalliope (WMF)@zhwiki Kanjy@jawiki Karol007@plwiki Karsten11@dewiki Kbrown (WMF)@arwiki Kbrown (WMF)@commonswiki Kbrown (WMF)@dewiki Kbrown (WMF)@enwiki Kbrown (WMF)@enwikibooks Kbrown (WMF)@enwikiversity Kbrown (WMF)@enwikivoyage Kbrown (WMF)@eowiki Kbrown (WMF)@eswiki Kbrown (WMF)@eswikibooks Kbrown (WMF)@frwiki Kbrown (WMF)@frwiktionary Kbrown (WMF)@itwiki Kbrown (WMF)@loginwiki Kbrown (WMF)@metawiki Kbrown (WMF)@ptwiki Kbrown (WMF)@simplewiki Kbrown (WMF)@testwiki Kbrown (WMF)@trwiki Kbrown (WMF)@zhwiki Keegan@enwiki Kegns@zhwiki Kelapstick@enwiki Kiran Gopi@mlwiki Kirill Lokshin@enwiki KnightLago@enwiki KnudW@dawiki Koavf@specieswiki KonstantinaG07@loginwiki KovacsUr@huwiki KrakatoaKatie@enwiki Krd@commonswiki Krd@loginwiki Krd@metawiki Ks aka 98@jawiki Ks0stm@enwiki Kulac@dewiki Kv75@ruwiki Kylu@enwiki Kylu@metawiki L235@enwiki LD@frwiki LFaraone@enwiki Laaknor@metawiki Laaknor@nowiki Ladsgroup@fawiki Ladsgroup@labswiki Ladsgroup@testwiki Ladypine@hewiki Lankiveil@enwiki Lanwi1@zhwiki Lar@alswiki Lar@anwiki Lar@arwiki Lar@bgwiki Lar@commonswiki Lar@dewiki Lar@enwiki Lar@enwikiquote Lar@enwikisource Lar@enwikiversity Lar@fawiki Lar@frwiki Lar@hywiki Lar@kywiktionary Lar@metawiki Lar@mswiki Lar@nlwiki Lar@nlwikiquote Lar@plwiki Lar@rowiki Lar@scowiki Lar@simplewiki Lar@simplewiktionary Lar@thwiki Lar@tiwiki Lar@trwiki Lar@ukwiki Lar@viwiki Lar@zhwiki Le chat perché@frwiki Lechatjaune@ptwiki Leinad pl@rowiki Leinad@metawiki Leinad@plwiki Lewisiscrazy@frwiki Lijealso@ptwiki Linedwell@frwiki Linedwell@loginwiki Liso@skwiki Little Sunshine@ptwiki Lofty abyss@abwiki Lofty abyss@acewiki Lofty abyss@astwiki Lofty abyss@bclwiki Lofty abyss@betawikiversity Lofty abyss@bnwiki Lofty abyss@cawiktionary Lofty abyss@chrwiki Lofty abyss@crwiki Lofty abyss@cswikibooks Lofty abyss@cswiktionary Lofty abyss@dawikisource Lofty abyss@dawiktionary Lofty abyss@dewikibooks Lofty abyss@dewikinews Lofty abyss@dewiktionary Lofty abyss@eewiki Lofty abyss@enwikibooks Lofty abyss@enwikiversity Lofty abyss@enwikivoyage Lofty abyss@etwiki Lofty abyss@fawiki Lofty abyss@glwiki Lofty abyss@huwiktionary Lofty abyss@idwikibooks Lofty abyss@idwikiquote Lofty abyss@idwikisource Lofty abyss@idwiktionary Lofty abyss@iewiki Lofty abyss@ilowiki Lofty abyss@incubatorwiki Lofty abyss@itwikisource Lofty abyss@itwikisource Lofty abyss@jvwiki Lofty abyss@kmwiki Lofty abyss@kowikiquote Lofty abyss@lawiki Lofty abyss@lbewiki Lofty abyss@lgwiki Lofty abyss@lmowiki Lofty abyss@lowiki Lofty abyss@lvwiki Lofty abyss@mediawikiwiki Lofty abyss@metawiki Lofty abyss@mnwiki Lofty abyss@mswiki Lofty abyss@ndswiki Lofty abyss@nlwikibooks Lofty abyss@nlwikiquote Lofty abyss@nlwiktionary Lofty abyss@nowiki Lofty abyss@nycwikimedia Lofty abyss@nywiki Lofty abyss@omwiki Lofty abyss@outreachwiki Lofty abyss@papwiki Lofty abyss@pflwiki Lofty abyss@pihwiki Lofty abyss@plwikiquote Lofty abyss@plwikisource Lofty abyss@ptwikibooks Lofty abyss@ptwikinews Lofty abyss@ptwikiquote Lofty abyss@ptwikiversity Lofty abyss@ptwikivoyage Lofty abyss@ptwiktionary Lofty abyss@quwiki Lofty abyss@simplewiki Lofty abyss@simplewiktionary Lofty abyss@sourceswiki Lofty abyss@specieswiki Lofty abyss@suwiki Lofty abyss@svwikinews Lofty abyss@svwikiquote Lofty abyss@swwiki Lofty abyss@tiwiktionary Lofty abyss@trwikisource Lofty abyss@vewiki Lofty abyss@warwiki Lofty abyss@wikidatawiki Lofty abyss@wuuwiki Lofty abyss@yiwiki Lofty abyss@yowiki Lofty abyss@zh_classicalwiki Lord Mota@ptwiki Luk@enwiki Luna Santin@enwiki Lusitana@ptwiki Lusum@itwiki Lymantria@commonswiki Lymantria@wikidatawiki Lzur@plwiki M7@kawiki M7@loginwiki M7@metawiki M7@simplewiki MAna (WMF)@testwiki MBisanz@enwiki MBq@dewiki MF-Warburg@loginwiki MFischer (WMF)@enwiki MGA73@dawiki MGodwin@enwiki MPelletier (WMF)@enwiki MPelletier (WMF)@wikidatawiki MPinchuk (WMF) (usurped)@mediawikiwiki MPostoronca-WMF@testwiki MSzabo-WMF@testwiki MZaplotnik@slwiki Mackensen@enwiki Madaki@itwiki Magister Mathematicae@akwiki Magister Mathematicae@alswiki Magister Mathematicae@angwiki Magister Mathematicae@arwiki Magister Mathematicae@aswiki Magister Mathematicae@azwiktionary Magister Mathematicae@barwiki Magister Mathematicae@bawiki Magister Mathematicae@bpywiki Magister Mathematicae@bugwiki Magister Mathematicae@cawiki Magister Mathematicae@chwiki Magister Mathematicae@commonswiki Magister Mathematicae@cowiki Magister Mathematicae@cswiki Magister Mathematicae@enwiki Magister Mathematicae@enwikinews Magister Mathematicae@eswiki Magister Mathematicae@eswikibooks Magister Mathematicae@eswikinews Magister Mathematicae@eswikiquote Magister Mathematicae@eswikisource Magister Mathematicae@eswikiversity Magister Mathematicae@fawiki Magister Mathematicae@ffwiki Magister Mathematicae@fiwikiquote Magister Mathematicae@fjwiki Magister Mathematicae@gawiktionary Magister Mathematicae@gdwiki Magister Mathematicae@itwikibooks Magister Mathematicae@kabwiki Magister Mathematicae@kowiki Magister Mathematicae@kwwiktionary Magister Mathematicae@metawiki Magister Mathematicae@nahwiki Magister Mathematicae@pihwiki Magister Mathematicae@ptwiki Magister Mathematicae@quwiki Magister Mathematicae@simplewiki Magister Mathematicae@simplewiktionary Magister Mathematicae@specieswiki Magister Mathematicae@stwiki Magister Mathematicae@tswiki Magister Mathematicae@ugwiki Magister Mathematicae@uzwiki Magister Mathematicae@zawiki MagnusA@svwiki Magog the Ogre@commonswiki Mailer diablo@enwiki Majorly@simplewiki Malatinszky@huwiki Marc Mongenet@frwiki MarcGarver@astwiki MarcGarver@azwikibooks MarcGarver@bxrwiki MarcGarver@cewiki MarcGarver@chrwiktionary MarcGarver@crwiki MarcGarver@cswikibooks MarcGarver@cswikiquote MarcGarver@enwikibooks MarcGarver@enwikiquote MarcGarver@enwikiversity MarcGarver@enwikivoyage MarcGarver@eswikiquote MarcGarver@fawiki MarcGarver@hawiki MarcGarver@hewikivoyage MarcGarver@hiwiki MarcGarver@idwikiquote MarcGarver@idwikisource MarcGarver@incubatorwiki MarcGarver@kabwiki MarcGarver@loginwiki MarcGarver@ltwiktionary MarcGarver@mediawikiwiki MarcGarver@mswiki MarcGarver@nlwikimedia MarcGarver@nlwikivoyage MarcGarver@nowiki MarcGarver@piwiki MarcGarver@ptwikivoyage MarcGarver@scowiki MarcGarver@slwiki MarcGarver@svwikivoyage MarcGarver@wuuwiki MarcGarver@zhwiki Marcelo Victor@ptwiki MarcoAurelio@astwiki MarcoAurelio@barwiki MarcoAurelio@chrwiki MarcoAurelio@eewiki MarcoAurelio@enwiki MarcoAurelio@eswikiquote MarcoAurelio@euwiki MarcoAurelio@glwiki MarcoAurelio@loginwiki MarcoAurelio@mediawikiwiki MarcoAurelio@metawiki MarcoAurelio@nowiki MarcoAurelio@ptwiki MarcoAurelio@ptwikiquote MarcoAurelio@sourceswiki MarcoAurelio@testwiki MarcoAurelio@zhwiki Mardetanha@commonswiki Mardetanha@enwiki Mardetanha@loginwiki Mardetanha@metawiki Marine-Blue@jawiki Mark Bergsma@enwiki Mark Bergsma@nlwiki Markov@frwiki Martin H.@commonswiki Martin Urbanec (WMF)@cswiki Martin Urbanec (WMF)@cswikiversity Martin Urbanec (WMF)@loginwiki Martin Urbanec (WMF)@metawiki Martin Urbanec (WMF)@testwiki Martin Urbanec@cswiki Martin Urbanec@enwiki Martin Urbanec@loginwiki Martin Urbanec@metawiki Martin Urbanec@testwiki Masti@loginwiki Masti@plwiki Matanya@enwiki Matanya@hewiki Matanya@testwiki Materialscientist@enwiki Mathis B@frwiki Mathonius@loginwiki Matiia@loginwiki MatthiasGor@plwiki Maurilbert@frwiki MaxSem@bgwiki MaxSem@elwiki MaxSem@enwiki MaxSem@enwikiquote MaxSem@enwikisource MaxSem@fowiktionary MaxSem@hrwiki MaxSem@metawiki MaxSem@nowiki MaxSem@nowikibooks MaxSem@plwiki MaxSem@shwiktionary MaxSem@simplewiki MaxSem@skwiki MaxSem@srwiki MaxSem@stwiki MaxSem@svwiki MaxSem@ukwiki MaxSem@yiwiki MaxSem@zhwiki Maxim@enwiki Mdennis (WMF)@commonswiki Mdennis (WMF)@dewiki Mdennis (WMF)@enwiki Mdennis (WMF)@enwikinews Mdennis (WMF)@enwikiquote Mdennis (WMF)@enwikisource Mdennis (WMF)@frwiki Mdennis (WMF)@frwiktionary Mdennis (WMF)@loginwiki Mdennis (WMF)@metawiki Mdennis (WMF)@simplewiki Mdennis (WMF)@svwiki MdsShakil@loginwiki MdsShakil@test2wiki MdsShakil@testwiki Melos@itwiki Melos@loginwiki Melos@sswiki Meno25@arwiki Mentisock@wikidatawiki Mercy@metawiki Meursault2004@idwiki Mido@arwiki Midom@enwiki Midom@metawiki Mike V@enwiki Mike.lifeguard@commonswiki Mike.lifeguard@enwikibooks Mike.lifeguard@metawiki Mike.lifeguard@pmswiki Mike.lifeguard@rmywiki Mike.lifeguard@tywiki MikkoM@fiwiki Millennium bug@ptwiki Millosh@azwiki Millosh@bswiki Millosh@cswiki Millosh@dewiktionary Millosh@enwiki Millosh@fawiki Millosh@gdwiktionary Millosh@hrwiki Millosh@kowiki Millosh@metawiki Millosh@pmswiki Millosh@ruwiki Millosh@ruwikisource Millosh@sqwiki Millosh@trwiki Millosh@zhwiki Minderbinder@dewiki Minorax@metawiki Mkdw@enwiki Mmenal@frwiki Moneytrees@enwiki Montgomery@eswiki Mormegil@cswiki Morven@enwiki MrJaroslavik@loginwiki MrJaroslavik@testwiki Msz2001@plwiki Mtarch11@itwiki Mtarch11@metawiki MuZemike@enwiki MusikAnimal@enwiki MusikAnimal@loginwiki Mwpnl@trwiki Mxn@viwiki Mykola7@loginwiki Mykola7@ukwiki Mys 721tx@zhwiki Mz7@enwiki NForrester (WMF)@bewiki NForrester (WMF)@bgwiki NForrester (WMF)@commonswiki NForrester (WMF)@enwiki NForrester (WMF)@eswiki NForrester (WMF)@metawiki NForrester (WMF)@ruwiki NForrester (WMF)@shwiki NForrester (WMF)@srwiki NForrester (WMF)@wikidatawiki NForrester (WMF)@zhwiki NKohli (WMF)@testwiki NNair (WMF)@enwiki NahidSultan (WMF)@arwiki NahidSultan (WMF)@arwikinews NahidSultan (WMF)@bnwiki NahidSultan (WMF)@bnwiktionary NahidSultan (WMF)@cawiki NahidSultan (WMF)@commonswiki NahidSultan (WMF)@enwiki NahidSultan (WMF)@enwikinews NahidSultan (WMF)@enwikiquote NahidSultan (WMF)@eswiki NahidSultan (WMF)@ffwiki NahidSultan (WMF)@foundationwiki NahidSultan (WMF)@frwiki NahidSultan (WMF)@hewiki NahidSultan (WMF)@jawiki NahidSultan (WMF)@jawiktionary NahidSultan (WMF)@knwiki NahidSultan (WMF)@kowiki NahidSultan (WMF)@loginwiki NahidSultan (WMF)@mediawikiwiki NahidSultan (WMF)@metawiki NahidSultan (WMF)@miwiki NahidSultan (WMF)@mkwiki NahidSultan (WMF)@mrwiki NahidSultan (WMF)@mswiki NahidSultan (WMF)@ptwiki NahidSultan (WMF)@rowiki NahidSultan (WMF)@ruwiki NahidSultan (WMF)@simplewiki NahidSultan (WMF)@specieswiki NahidSultan (WMF)@srwiki NahidSultan (WMF)@tawiki NahidSultan (WMF)@ukwiki NahidSultan (WMF)@uzwiki NahidSultan (WMF)@wikidatawiki NahidSultan (WMF)@zhwiki NahidSultan@bnwiki NahidSultan@loginwiki Nakor@frwiki Nataev@uzwiki NativeForeigner@enwiki Natuur12@nlwiki Nedops@plwiki Nelson Teixeira@ptwiki Nettadi@hewiki Neukoln@hewiki Newyorkbrad@enwiki Nick1915@bat_smgwiki Nick1915@elwiki Nick1915@fawiki Nick1915@itwikibooks Nick1915@itwiktionary Nick1915@kowiki Nick1915@kwwiki Nick1915@lmowiki Nick1915@metawiki Nick1915@ptwiki Nick1915@scnwiki Nick1915@trwiki Nick1915@zhwiki NickK@ukwiki NinjaRobotPirate@enwiki Nishkid64@enwiki NoFWDaddress@frwiki Nohirara@idwiki Noumenon@trwiki NuclearWarfare@enwiki Nuno Tavares@ptwiki Nux@plwiki Nyenyec@huwiki OJJ@cswiki Octahedron80@thwiki OhanaUnited@enwikivoyage OneLittleMouse@ruwiki OnlyJonny@ptwiki Opabinia regalis@enwiki Operator873@loginwiki Operator873@simplewiki Ori@hewiki Oscar@commonswiki Oscar@dewiki Oscar@enwiki Oscar@frwiki Oscar@metawiki Oscar@nlwiki Oscar@nlwikibooks Oscarami@eswiki Oshwah@enwiki PEarley (WMF)@enwiki PSaxena (WMF)@mediawikiwiki PSaxena (WMF)@testwiki Paginazero@commonswiki Paginazero@enwiki Paginazero@enwikiquote Paginazero@itwiki Paginazero@itwikibooks Paginazero@ruwiki Paginazero@yiwiki Pallerti@huwiki Palnatoke@dawiki Pap3rinik@itwiki Pathoschild@commonswiki Pathoschild@cywiki Pathoschild@elwiki Pathoschild@enwiki Pathoschild@enwikinews Pathoschild@enwikiquote Pathoschild@enwikisource Pathoschild@eowiki Pathoschild@eswiki Pathoschild@eswikiquote Pathoschild@etwiki Pathoschild@fawiki Pathoschild@fiwikinews Pathoschild@hewiki Pathoschild@hsbwiki Pathoschild@huwiki Pathoschild@idwiki Pathoschild@incubatorwiki Pathoschild@itwiki Pathoschild@itwikiquote Pathoschild@klwiki Pathoschild@kwwiktionary Pathoschild@lbwiki Pathoschild@metawiki Pathoschild@miwiki Pathoschild@mlwiki Pathoschild@nlwiki Pathoschild@nnwiki Pathoschild@ocwiki Pathoschild@ptwiki Pathoschild@ruwiktionary Pathoschild@simplewiki Pathoschild@siwiki Pathoschild@sqwiki Pathoschild@srwiki Pathoschild@urwiktionary Pathoschild@vewiki Pathoschild@yiwiki Pathoschild@zhwiki Pathoschild@zhwiktionary PatríciaR@ptwiki Penn Station@jawiki Perrak@dewiki Pete Forsyth (WMF)@enwiki PeterSymonds@metawiki Peterdownunder@simplewiki PhilKnight@enwiki Philippe (WMF)@azwiki Philippe (WMF)@commonswiki Philippe (WMF)@dewiki Philippe (WMF)@enwiki Philippe (WMF)@enwikibooks Philippe (WMF)@enwikinews Philippe (WMF)@enwikiquote Philippe (WMF)@enwikisource Philippe (WMF)@enwikiversity Philippe (WMF)@enwikivoyage Philippe (WMF)@eswiki Philippe (WMF)@fawiki Philippe (WMF)@frwiki Philippe (WMF)@frwiktionary Philippe (WMF)@itwiki Philippe (WMF)@loginwiki Philippe (WMF)@mediawikiwiki Philippe (WMF)@metawiki Philippe (WMF)@mgwiktionary Philippe (WMF)@nlwiki Philippe (WMF)@simplewiki Philippe (WMF)@wikidatawiki Philippe (WMF)@xhwiki Philippe (WMF)@zhwiki Philippe@enwiki Piku@frwiki Pmlineditor@loginwiki Polimerek@plwiki Polimerek@plwikimedia Ponyo@enwiki Postrach@cswiki Poudou!@frwiki Pouyana@fawiki Premeditated Chaos@enwiki Primefac@enwiki Prométhée@frwiki Ptjackyll@plwiki Pundit@plwiki PurpleBuffalo@hewiki Putnik@ruwiki Q-bit array@ruwiki Queix@frwiki RLuts@ukwiki RadiX@bhwiki RadiX@cswikisource RadiX@enwikiversity RadiX@hiwiki RadiX@loginwiki RadiX@pswiki RadiX@ptwiki RadiX@ptwikibooks RadiX@ptwikiquote RadiX@svwikiversity Rastrojo@eswiki Raul654@enwiki Rax@dewiki Razimantv@mlwiki Rdsmith4@arwiki Rdsmith4@biwiki Rdsmith4@etwiki Rdsmith4@fawiki Rdsmith4@gawiki Rdsmith4@huwiki Rdsmith4@idwiki Rdsmith4@jawiki Rdsmith4@kowiki Rdsmith4@lmowiki Rdsmith4@lvwiki Rdsmith4@metawiki Rdsmith4@newiktionary Rdsmith4@nrmwiki Rdsmith4@rowiki Rdsmith4@rowiktionary Rdsmith4@scowiki Rdsmith4@trwiki Rdsmith4@zhwiki ReAl@ukwiki Reaper Eternal@enwiki Redux@enwiki Reedy (WMF)@arwiki Reedy (WMF)@enwiki Reedy (WMF)@mediawikiwiki Reedy (WMF)@metawiki Reedy (WMF)@testwiki Reedy@commonswiki Reedy@enwiki Reedy@itwiki Reedy@labswiki Reedy@wikidatawiki Rei-artur@ptwiki Renamed user mou89p43twvqcvm8ut9w3@enwiki Revi C.@betawikiversity Revi C.@dewikiquote Revi C.@enwiki Revi C.@enwikinews Revi C.@enwikivoyage Revi C.@eswikinews Revi C.@kowiktionary Revi C.@loginwiki Revi C.@mediawikiwiki Revi C.@rowiki Revi C.@ruwikinews Revi C.@ruwikiquote Revi C.@smnwiki Revi C.@zh_classicalwiki Revi C.@zhwiki Rexcornot@frwiki Reza1615@fawiki RiazACU@bnwiki Richwales@enwiki Richwales@eswiki RickinBaltimore@enwiki Risker@enwiki Rlevse@enwiki RobLa-WMF@enwiki RobLa-WMF@metawiki RobLa-WMF@plwiki Rodasmith@enwiktionary Roger Davies@enwiki Rojelio@itwiki Romihaitza@simplewiki Romihaitza@yiwiki RoySmith@arwiki RoySmith@enwiki RoySmith@metawiki Rsocol@rowiki Rubin16@ruwiki Ruslik0@loginwiki Ruslik0@nlwikibooks Ruthven@itwiki Ruthven@testwiki Ruy Pugliesi@mediawikiwiki Rxy@loginwiki Rxy@wikidatawiki Ryan Kaldari (WMF)@commonswiki Ryan Kaldari (WMF)@enwiki Ryan Lane (WMF)@labswiki SB Johnny@commonswiki SB Johnny@enwikibooks SB Johnny@enwikiversity SBJohnny@enwikibooks SBassett (WMF)@enwiki SBassett (WMF)@mediawikiwiki SBassett (WMF)@metawiki SBassett (WMF)@testwiki SEPRodrigues@ptwiki SHB2000@enwikivoyage SHB2000@loginwiki SMP@cawiki SNg (WMF)@enwiki SNg (WMF)@metawiki SNg (WMF)@nlwiki SPoore (WMF)@enwiki SPoore (WMF)@hrwiki SPoore (WMF)@itwiki SPoore (WMF)@metawiki SPoore (WMF)@simplewiki SPoore (WMF)@testwiki SQL@enwiki SSandu-WMF@enwiki SSpalding (WMF)@enwiki ST47@enwiki ST47@testwiki Sadrettin@trwiki Sahehco@fawiki Sahehco@testwiki Sakretsu@itwiki Sakretsu@loginwiki Salvio giuliano@enwiki Sam Korn@enwiki Samuel (WMF)@enwiki Samuel (WMF)@eswiki Samuel (WMF)@frwiki Samuel (WMF)@iawiki Samuel (WMF)@metawiki Samuel (WMF)@testwiki Samuel (WMF)@ukwiki Saper@plwiki Savh@itwikibooks Savh@loginwiki Sbisolo@itwiki Schiste@frwiki Schlum@frwiki Schniggendiller@loginwiki ScottishFinnishRadish@enwiki Sdrqaz@enwiki SelfieCity@enwikivoyage Seraphimblade@enwiki Shanel@aawiki Shanel@arwiki Shanel@cswiki Shanel@cswikiquote Shanel@eewiki Shanel@elwikibooks Shanel@enwiki Shanel@enwikiquote Shanel@eswikisource Shanel@fawiki Shanel@frwikinews Shanel@frwiktionary Shanel@igwiki Shanel@itwikinews Shanel@jawikinews Shanel@kowiki Shanel@mlwiki Shanel@ndswiki Shanel@ptwiki Shanel@ptwikinews Shanel@ptwiktionary Shanel@simplewiktionary Shanel@skwiki Shanel@skwiktionary Shanel@tiwiki Shanel@ukwiki Shanel@vecwiki Shanel@yiwiki Shanel@zhwiki Shanmugamp7@enwiki Shanmugamp7@loginwiki Shell Kinney@enwiki Shivanarayana@itwiki Shizhao@betawikiversity Shizhao@commonswiki Shizhao@dewiki Shizhao@enwiki Shizhao@frwiki Shizhao@kowiki Shizhao@kowikisource Shizhao@metawiki Shizhao@ptwikiquote Shizhao@ruwiki Shizhao@viwiki Shizhao@zhwiki Siam2019@thwiki SilkTork@enwiki SilverLocust@enwiki Sir Lestaty de Lioncourt@ptwikinews Sir kiss@hewiki Sir48@commonswiki Sir48@dawiki SirFozzie@enwiki Sjoerddebruin@loginwiki Skenmy@enwikinews Slaporte (WMF)@commonswiki Slaporte (WMF)@enwiki Slaporte (WMF)@ptwiki Smooth O@bswiki Snowdog@itwiki SoWhy@enwiki Sotiale@enwiki Sotiale@kowiki Sotiale@loginwiki Sotiale@metawiki Sotiale@wikidatawiki Spacebirdy@anwiki Spacebirdy@cawiki Spacebirdy@metawiki Spacebirdy@testwiki Spangineer@enwikisource SpeedyGonsales@hrwiki Spicy@enwiki Squasher@dewiki Stanglavine@loginwiki Stanglavine@metawiki Stanglavine@ptwiki Stephen Bain@enwiki Steve Smith@enwiki Stigfinnare@svwiki Strainu@rowiki Stryn@fiwiki Stryn@loginwiki Stwalkerster@enwiki Suisui@jawiki Superpes15@enwiki Superpes15@enwikiquote Superpes15@fawiki Superpes15@itwiki Superpes15@itwikiversity Superpes15@loginwiki Superpes15@metawiki Superspritz@itwiki Surjection@enwiktionary Syp@huwiki Szwedzki@plwiki TBolliger (WMF)@testwiki THargrove (WMF)@plwiki Tarawneh@arwiki Taweetham@thwiki Tbone@fiwiki Tegel@enwiki Tegel@eswiktionary Tegel@loginwiki Tegel@metawiki Tegel@svwiki Teles@enwiki Teles@loginwiki Teles@metawiki Teles@ptwiki Tfinc@enwiki Tfinc@metawiki Tgr (WMF)@commonswiki Tgr (WMF)@enwiki Tgr (WMF)@loginwiki Tgr (WMF)@metawiki Tgr (WMF)@testwiki Tgr (WMF)@trwiki Tgr@huwiki Thatcher@enwiki The Epopt@enwiki The Rambling Man@simplewiki The Squirrel Conspiracy@commonswiki TheDaveRoss@enwiktionary TheStriker@hewiki Theghaz@dewiki Theleekycauldron@enwiki Thenub314@enwikibooks TheresNoTime@enwiki TheresNoTime@loginwiki TheresNoTime@simplewiki Thogo@afwiki Thogo@alswiki Thogo@arzwiki Thogo@azwiktionary Thogo@barwiki Thogo@bat_smgwiki Thogo@bawiki Thogo@bgwiki Thogo@biwiki Thogo@brwiki Thogo@diqwiki Thogo@eewiki Thogo@elwiktionary Thogo@emlwiki Thogo@enwikisource Thogo@enwikiversity Thogo@enwiktionary Thogo@eowiki Thogo@eswiktionary Thogo@euwiki Thogo@euwiktionary Thogo@fawiki Thogo@fowiktionary Thogo@frwikinews Thogo@frwikiquote Thogo@fywiki Thogo@gawiki Thogo@gdwiktionary Thogo@gvwiktionary Thogo@hiwiki Thogo@hrwiki Thogo@huwiki Thogo@hywiki Thogo@itwikiquote Thogo@itwiktionary Thogo@iuwiki Thogo@jawikinews Thogo@jawikisource Thogo@jawiktionary Thogo@kmwiki Thogo@kowiki Thogo@liwiki Thogo@mediawikiwiki Thogo@miwiki Thogo@ndswiki Thogo@nnwiki Thogo@nowiki Thogo@pamwiki Thogo@ptwikinews Thogo@quwiki Thogo@rowiki Thogo@simplewiktionary Thogo@siwiki Thogo@sourceswiki Thogo@sowiki Thogo@specieswiki Thogo@stqwiki Thogo@vecwiki Thogo@viwiki Thogo@vowiki Thogo@wawiki Thogo@xhwiki Thogo@yiwiki Thogo@zhwiki Thryduulf@enwiki Tim Starling (WMF)@dewiki Tim Starling (WMF)@enwiki Tim Starling (WMF)@mediawikiwiki Tim Starling (WMF)@ruwiki Tim Starling@enwiki Tim Starling@labswiki Tim Starling@metawiki Tim Starling@testwiki Timekeepertmk@thwiki Timotheus Canens@enwiki Tinz@dewiki Tiptoety@commonswiki Tiptoety@enwiki Tiptoety@metawiki Titore@itwiki Tks4Fish@enwiki Tks4Fish@loginwiki Tks4Fish@ptwiki Tnxman307@enwiki ToBeFree@enwiki Tom Morris@enwikinews Tomer T@hewiki TonyBallioni@enwiki Toto Azéro@frwiki Triglav@jawiki Trijnstel@commonswiki Trijnstel@enwiki Trijnstel@loginwiki Trijnstel@metawiki TunnelESON@enwikiquote TunnelESON@fawiki TunnelESON@frwiktionary TunnelESON@jawikibooks TunnelESON@jawiktionary TunnelESON@kowiki TunnelESON@kowikisource TunnelESON@metawiki TunnelESON@mlwiki TunnelESON@ptwiki TunnelESON@ruwiki TunnelESON@siwiki TunnelESON@thwiki TunnelESON@ukwiki TunnelESON@viwiki TunnelESON@zhwiki Tznkai@enwiki Ugur Basak@trwiki Uncitoyen@trwiki UninvitedCompany@enwiki Ustad abu gosok@idwiki VIGNERON@loginwiki VWalters-WMF@enwiki VWalters-WMF@testwiki Vanamonde93@enwiki Vanished user 5zariu3jisj0j4irj@enwiki Vassyana@enwiki Vermont@loginwiki Vermont@metawiki Vermont@simplewiki Versageek@enwiki Versageek@enwiktionary VictorAnyakin@ukwiki Vigorous action@jawiki Vincent Vega@trwiki Viniciusmc@ptwiki Vituzzu@enwiki Vituzzu@eswikiquote Vituzzu@itwiki Vituzzu@metawiki Vituzzu@ptwikibooks Vlad@rowiki Vodomar@hrwiki W.CC@jawiki WBrown (WMF)@test2wiki WBrown (WMF)@testcommonswiki WBrown (WMF)@testwiki WMFOffice@arwiki WMFOffice@commonswiki WMFOffice@enwiki WMFOffice@fawiki WMFOffice@frwiki WMFOffice@loginwiki WMFOffice@mediawikiwiki WMFOffice@metawiki WMFOffice@ruwiki WMFOffice@wikidatawiki Wagino 20100516@idwiki Walter@brwiki Walter@nlwiki Walter@nlwikibooks Walter@testwiki WarX@plwiki Wegge@dawiki Werdna@enwiki Werdna@enwikisource Werdna@mediawikiwiki Whiteknight@enwikibooks Wiki13@loginwiki Wikimol@cswiki Wikitanvir@bnwiki WilliamH@enwiki Wim b@loginwiki Wind@ruwiki Wizardman@enwiki Wojciech Pędzich@betawikiversity Wojciech Pędzich@commonswiki Wojciech Pędzich@dewiktionary Wojciech Pędzich@eewiki Wojciech Pędzich@enwikiquote Wojciech Pędzich@enwikiversity Wojciech Pędzich@fawiki Wojciech Pędzich@fiwikibooks Wojciech Pędzich@itwikinews Wojciech Pędzich@kowiki Wojciech Pędzich@ltwiktionary Wojciech Pędzich@nlwikimedia Wojciech Pędzich@nowiki Wojciech Pędzich@outreachwiki Wojciech Pędzich@plwiki Wojciech Pędzich@plwikibooks Wojciech Pędzich@plwikimedia Wojciech Pędzich@plwikinews Wojciech Pędzich@plwikiquote Wojciech Pędzich@plwiktionary Wojciech Pędzich@rowiki Wojciech Pędzich@simplewiktionary Wojciech Pędzich@szlwiki Wojciech Pędzich@ukwiki Wojciech Pędzich@xhwiki Worm That Turned@enwiki Wugapodes@enwiki Wulfson@ruwiki XXBlackburnXx@enwiki XXBlackburnXx@loginwiki XXBlackburnXx@nlwiki Xania@enwikibooks Xaosflux@loginwiki Xaosflux@metawiki Xeno@enwiki Y-dash@jawiki Yahya@loginwiki Yamla@enwiki Yann@arwiki Yann@commonswiki Yann@enwikisource Yann@frwikinews Yann@hiwiki Yann@itwikiversity Yann@metawiki Yann@sourceswiki Yann@ukwiki YellowMonkey@enwiki Yunshui@enwiki Z1720@enwiki ZExley (WMF)@enwiki ZSoo (WMF)@enwiki Zabe@enwiki Zabe@mediawikiwiki Zabe@test2wiki Zabe@testcommonswiki Zabe@testwiki Zafer@trwikimedia Zzuuzz@enwiki Érico@ptwiki Боки@srwiki Ле Лой@ruwiki Обрадовић Горан@srwiki Стефанко1982@ukwiki בריאן@hewiki דגש@hewiki דולב@hewiki דניאל ב.@hewiki יונה בנדלאק@hewiki ירון@hewiki מוטי@hewiki מתניה@zhwiki עוזי ו.@hewiki עידו@hewiki עli@hewiki רחל1@hewiki باسم@arwiki جار الله@arwiki صالح@arwiki علاء@arwiki علاء@enwiki علاء@loginwiki علاء@wikidatawiki فيصل@arwiki ميموني@arwiki আফতাবuজ্জামান@bnwiki さかおり@jawiki 柏尾菓子@jawiki 欅@jawiki 海獺@jawiki 이강철@kowiki `; // PASTE YOUR LIST HERE function getDomain(db) { const mapping = { 'metawiki': 'meta.wikimedia.org', 'commonswiki': 'commons.wikimedia.org', 'wikidatawiki': 'www.wikidata.org', 'loginwiki': 'login.wikimedia.org', 'sourceswiki': 'wikisource.org', 'specieswiki': 'species.wikimedia.org', 'incubatorwiki': 'incubator.wikimedia.org', 'foundationwiki': 'foundation.wikimedia.org', 'outreachwiki': 'outreach.wikimedia.org' }; if (mapping[db]) return mapping[db]; if (db.includes('wikimania')) { const year = db.match(/\d+/); return year ? `wikimania${year}.wikimedia.org` : 'wikimania.wikimedia.org'; } const projects = ['wiktionary', 'wikibooks', 'wikiquote', 'wikinews', 'wikiversity', 'wikivoyage']; for (const p of projects) { if (db.endsWith(p)) return `${db.replace(p, '').replace(/_/g, '-')}.${p}.org`; } return `${db.replace('wiki', '').replace(/_/g, '-')}.wikipedia.org`; } function initInterface() { document.title = "GCUS Data Generator"; const $body = $('#mw-content-text'); $body.empty(); $body.append(` <div style="background:white; border:1px solid #a2a9b1; padding:20px; font-family:sans-serif; color:#202122;"> <h1 style="margin-top:0; border-bottom:1px solid #a2a9b1; padding-bottom:10px;">GCUS Data Generator v6.9.4</h1> <p>Generates Lua data for <code>Module:GlobalRoles/Data</code>. <br> <b>Filter:</b> Local CU < 60 days ignored. <b>Format:</b> YYYY-MM-DD. <b>Auto-skip unreachable wikis.</b></p> <div style="display:flex; gap:15px; margin-bottom:20px;"> <div style="flex:1;"> <strong>Input List:</strong> <textarea id="gcusInput" style="width:100%; height:180px; font-family:monospace; font-size:12px; border:1px solid #a2a9b1; margin-top:5px;">${USERS_LIST}</textarea> </div> <div style="flex:1;"> <strong>Progress Log:</strong> <div id="gcusLog" style="height:180px; overflow-y:scroll; background:black; color:#0f0; font-family:monospace; font-size:11px; padding:5px; border-radius:3px; margin-top:5px;">Ready...</div> </div> <div style="width:200px;"> <strong>Failed Wikis:</strong> <textarea id="gcusFailed" readonly style="width:100%; height:180px; font-family:monospace; font-size:11px; background:#fff0f0; border:1px solid #fcc; margin-top:5px; color:#a00;"></textarea> </div> </div> <button id="gcusStart" style="padding:10px 24px; background:#36c; color:white; border:none; cursor:pointer; font-weight:bold;">START SCAN</button> <span id="gcusStatus" style="margin-left:15px; font-weight:bold;"></span> <div id="gcusResultArea" style="display:none; margin-top:20px;"> <strong>Lua Output:</strong><br> <textarea id="gcusOutput" style="width:100%; height:350px; font-family:monospace; font-size:12px; background:#fffbe6; border:1px solid #f2e394; margin-top:5px;"></textarea> </div> </div> `); $('#gcusStart').on('click', runGenerator); } async function runGenerator() { const input = $('#gcusInput').val(); const $log = $('#gcusLog'); const $output = $('#gcusOutput'); const $status = $('#gcusStatus'); const $btn = $('#gcusStart'); const $failed = $('#gcusFailed'); $btn.prop('disabled', true).css('background', '#a2a9b1').text('SCANNING...'); $log.empty(); $failed.val(''); $('#gcusResultArea').show(); const log = (msg) => { $log.append($('<div>').text(`[${new Date().toLocaleTimeString()}] ${msg}`)); $log.scrollTop($log[0].scrollHeight); }; const sleep = ms => new Promise(r => setTimeout(r, ms)); async function safeFetch(url, wikiName, attempts = 0) { await sleep(800); const fullUrl = url + "&origin=*&contact=User:MrJaroslavik"; try { const response = await fetch(fullUrl); if (response.status === 429) { const retry = response.headers.get('Retry-After') || 60; log(`Rate limit! Waiting ${retry}s...`); await sleep(retry * 1000); return safeFetch(url, wikiName, attempts); } if (!response.ok) throw new Error(`HTTP ${response.status}`); return await response.json(); } catch (e) { if (attempts < 2) { log(`Retrying ${wikiName} (${attempts + 1}/3)...`); await sleep(2000); return safeFetch(url, wikiName, attempts + 1); } log(`!! Skipped: ${wikiName} (${e.message})`); const currentFailed = $failed.val(); if (!currentFailed.includes(wikiName)) { $failed.val(currentFailed + wikiName + '\n'); } return null; } } function checkChange(logEntry, roleName) { const p = logEntry.params; if (!p) return null; const r = roleName.toLowerCase(); const normalize = (v) => Array.isArray(v) ? v.join(',').toLowerCase() : String(v || "").toLowerCase(); const oldStr = normalize(p.oldGroups || p.oldgroups || p.remove || p[0]); const newStr = normalize(p.newGroups || p.newgroups || p.add || p[1]); const hasRole = (s) => new RegExp('(^|,|\\s)' + r + '($|,|\\s)').test(s); if (!hasRole(oldStr) && hasRole(newStr)) return "added"; if (hasRole(oldStr) && !hasRole(newStr)) return "removed"; return null; } function getDaysDiff(dateFrom, dateTo) { if (dateTo === 'present') return 999; const d1 = new Date(dateFrom); const d2 = new Date(dateTo); return Math.floor((d2 - d1) / (1000 * 60 * 60 * 24)); } const localArray = input.split('\n').map(s => s.trim()).filter(s => s.length > 0); const globalArray = [...new Set(localArray.map(s => { const parts = s.split('@'); parts.pop(); return parts.join('@'); }))]; let rawData = {}; log(`Started. Processing ${globalArray.length} unique users.`); // 1. GLOBAL ROLES for (let i = 0; i < globalArray.length; i++) { const name = globalArray[i]; const gDb = 'global'; $status.text(`Global: ${i+1}/${globalArray.length} (${name})`); let continueToken = ''; while (continueToken !== undefined) { const data = await safeFetch(`https://meta.wikimedia.org/w/api.php?action=query&list=logevents&letype=gblrights&letitle=User:${encodeURIComponent(name)}&lelimit=max${continueToken}&format=json`, 'metawiki'); if (!data) break; const logs = (data.query && data.query.logevents ? data.query.logevents : []).reverse(); logs.forEach(l => { const date = l.timestamp.substring(0, 10); ['steward', 'staff', 'ombuds'].forEach(role => { const change = checkChange(l, role); if (change === "added") { if (!rawData[name]) rawData[name] = []; rawData[name].push({ role, db: gDb, from: date, to: 'present' }); } else if (change === "removed") { const entries = rawData[name] || []; const last = entries.filter(e => e.role === role && e.to === 'present').pop(); if (last) last.to = date; } }); }); continueToken = data.continue ? `&lecontinue=${data.continue.lecontinue}` : undefined; } } // 2. LOCAL ROLES for (let i = 0; i < localArray.length; i++) { const parts = localArray[i].split('@'); const lDb = parts.pop(); const name = parts.join('@'); const userEntries = rawData[name] || []; if (lDb === 'loginwiki' && userEntries.some(e => e.db === 'global' && e.role === 'steward')) continue; $status.text(`Local: ${i+1}/${localArray.length} (${name}@${lDb})`); let continueToken = ''; const domain = getDomain(lDb); try { while (continueToken !== undefined) { const data = await safeFetch(`https://${domain}/w/api.php?action=query&list=logevents&letype=rights&letitle=User:${encodeURIComponent(name)}&lelimit=max${continueToken}&format=json`, lDb); if (!data) break; const logs = (data.query && data.query.logevents ? data.query.logevents : []).reverse(); logs.forEach(l => { const date = l.timestamp.substring(0, 10); const change = checkChange(l, 'checkuser'); if (change === "added") { if (!rawData[name]) rawData[name] = []; rawData[name].push({ role: 'cu', db: lDb, from: date, to: 'present' }); } else if (change === "removed") { const entries = rawData[name] || []; const last = entries.filter(e => e.role === 'cu' && e.db === lDb && e.to === 'present').pop(); if (last) last.to = date; } }); continueToken = data.continue ? `&lecontinue=${data.continue.lecontinue}` : undefined; } } catch(e) { log(`Error ${name}@${lDb}: ${e.message}`); } } // 3. GENERATE LUA let lua = "return {\n users = {\n"; const sortedUsers = Object.keys(rawData).sort(); for (const user of sortedUsers) { const filteredEntries = rawData[user].filter(e => { if (e.db === 'global') return true; if (e.to === 'present') return true; return getDaysDiff(e.from, e.to) >= 60; }); if (filteredEntries.length > 0) { lua += ` ["${user}"] = {\n`; filteredEntries.forEach(e => { lua += ` { role = "${e.role}", db = "${e.db}", from = "${e.from}", to = "${e.to}" },\n`; }); lua += ` },\n`; } } lua += " }\n}"; $output.val(lua); $status.text("FINISHED!"); log("Scan complete. Unreachable projects were reported."); $btn.prop('disabled', false).css('background', '#36c').text('RUN AGAIN'); } $(initInterface); })(); /* </nowiki> */ bgr4rq3e3lj3ik3fhkkxzaaa6r6wmlx 739783 739778 2026-04-29T16:23:04Z MrJaroslavik 44012 + 739783 javascript text/javascript /* <nowiki> */ /** * */ (function() { if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || mw.config.get('wgTitle').split('/').pop() !== 'GCUS-Data') { return; } const USERS_LIST = `.anaconda@kvwiki .anaconda@tiwiki .mau.@itwiki 0x010C@frwiki 1234qwer1234qwer4@loginwiki 1997kB@wikidatawiki 3(MG)²@frwiki A09@loginwiki A09@slwiki ABX@plwiki AJones (WMF)@enwiki AJones (WMF)@metawiki AKarani (WMF)@enwiki AKoval (WMF)@enwiki ATripathi-WMF@enwiki ATripathi-WMF@eswiki Aaron Schulz@enwiki Abisys@itwiki Acagastya@commonswiki Acagastya@enwikinews Adailton@ptwiki AdiJapan@rowiki Adler.fa@fawiki Adrian@skwiki Adrignola@enwikibooks Agony@fiwiki Ajraddatz@commonswiki Ajraddatz@enwiki Ajraddatz@eswiki Ajraddatz@loginwiki Ajraddatz@metawiki Akeron@frwiki Akoopal@nlwiki Alain r@frwiki Alan@commonswiki Alan@eswiki Alan@metawiki Albertoleoncio@loginwiki Albertoleoncio@ptwiki Aldnonymous@idwiki Alex Shih@enwiki Alexander Misel@zhwiki Alexanderps@ptwiki Alexanderps@ptwikinews Alhen@eswiki Alison@enwiki Alnokta@arwiki Alphax@commonswiki Alraunenstern@dewiki Amalthea@enwiki AmandaNP@enwiki AmandaNP@hrwiki AmandaNP@itwiki AmandaNP@loginwiki AmandaNP@wikidatawiki Andre Engels@acewiki Andre Engels@anwiki Andre Engels@barwiki Andre Engels@bgwiki Andre Engels@bswiki Andre Engels@dewiki Andre Engels@eswiktionary Andre Engels@fawiki Andre Engels@fawikiquote Andre Engels@kowiki Andre Engels@lvwiki Andre Engels@mswiki Andre Engels@nlwiki Andre Engels@ptwiki Andre Engels@ptwiktionary Andre Engels@sourceswiki Andre Engels@specieswiki Andre Engels@testwiki Andre Engels@urwiki Andre Engels@viwiki Andre Engels@zeawiki Andre Engels@zhwiki Andrei Stroe@rowiki Andrej Šalov@hrwiki Andriy.v@ukwiki Annabel@nlwiki Anr@fiwiki Anthere@enwiki Anthere@metawiki AntiCompositeNumber@loginwiki Antime@arwiki Aoidh@enwiki Aoineko@frwiki Aphaia@enwikiquote AramilFeraxa@loginwiki AramilFeraxa@metawiki AramilFeraxa@plwiki Aratal@frwiki Arcticocean@enwiki Arcticocean@hrwiki Arcticocean@kowiki Arcticocean@ptwiki Arcticocean@ukwiki Argo Navis@hrwiki Ash Crow@frwiki Asilvering@enwiki Ask21@itwiki Ausir@plwiki Avocato@arwiki Avraham@enwiki Avraham@loginwiki Azafred@enwiki B20180@thwiki BDD@enwiki BRPever@loginwiki BRPever@simplewiki BRPever@wikidatawiki Bahamut0013@enwiki Barak a@hewiki Barcex@eswiki Barkeep49@enwiki Barras@enwiki Barras@loginwiki Barras@metawiki Barras@simplewiki Base@loginwiki Bastique@azwiki Bastique@commonswiki Bastique@crwiki Bastique@enwiki Bastique@enwikibooks Bastique@enwikiquote Bastique@enwikiversity Bastique@eswiki Bastique@nowiki Bastique@plwiki Bastique@ruwiki Bastique@simplewiki Bastique@stwiki Bastique@svwiki Bastique@testwiki Bastique@trwiki Bastique@wikimania2008wiki Bastique@yiwiki Bastique@zhwiki Bbb23@enwiki Bdk@dewiki Beeblebrox@enwiki Bellcricket@jawiki Bencmq@zhwiki Benevolent Otter@plwiki Bennylin@idwiki Benoît Prieur@frwiki Berean Hunter@enwiki Beria@ptwiki Bernard@eswiki BetoCG@eswiki Billinghurst@enwikisource Billinghurst@eswikivoyage Billinghurst@loginwiki Billinghurst@metawiki Billinghurst@plwikinews Billinghurst@slwiki Bináris2@huwiki Bináris@huwiki Biologo32@ptwiki Blablubbs@enwiki BlackBeast@eswiki Borgx@idwiki Bradv@enwiki Brandon@enwiki BraneJ@srwiki Brian McNeil@enwikinews Brian@enwikinews Brooke Vibber@dewiki Brooke Vibber@enwiki Brooke Vibber@frwiki Brooke Vibber@mediawikiwiki Brooke Vibber@testwiki Bryan@commonswiki BryanDavis@labswiki Bsadowski1@loginwiki Bsadowski1@metawiki Bsadowski1@simplewiki CAshraf (WMF)@enwiki CLo (WMF)@testwiki CMaslak (WMF)@enwiki CMaslak (WMF)@fawiki CMaslak (WMF)@srwiki CMaslak (WMF)@uzwiki CPettet (WMF)@labswiki CSteigenberger (WMF)@cawiki CSteigenberger (WMF)@enwiki CSteigenberger (WMF)@jawiki CSteigenberger (WMF)@metawiki CSteipp (WMF)@ptwiktionary Cabayi@enwiki Caesar@svwiki Callanecc@enwiki CaptainEek@enwiki Carkuni@jawiki Cartman02au@metawiki Cary Bass@arwiki Cary Bass@aswiki Cary Bass@commonswiki Cary Bass@enwiki Cary Bass@enwikiquote Cary Bass@enwikiversity Cary Bass@frwiki Cary Bass@hiwiki Cary Bass@itwiki Cary Bass@metawiki Cary Bass@ptwiki Casliber@enwiki Cassiopeia sweet@jawiki Cato@enwikiquote Cdip150@zhwiki Cgt@dawiki Chaos@arwiki Chase me ladies, I'm the Cavalry@enwiki Chatama@jawiki Christian List@dawiki Christine (WMF)@enwiki Chuck Entz@enwiktionary Cinabrium@eswiki Ciphers@arwiki Cirdan@dewiki Cirt@enwikinews Civvi~itwiki@itwiki Clem23@frwiki Codc@dewiki CodeMonk@ruwiki Coet@cawiki Conde Edmond Dantès@ptwiki Connel MacKenzie@enwiktionary Cool Hand Luke@enwiki Coren@enwiki Coren@labswiki Count Count@dewiki Count Count@loginwiki Courcelles@enwiki Creol@simplewiki Cromium@elwiki Cromium@enwikinews Cromium@loginwiki Cromium@rowiki Cromium@sqwiki Cromium@zhwiki Cruccone@itwiki Csigabi@huwiki Cspurrier@enwikinews Cspurrier@enwikiquote Cspurrier@enwikiversity Cspurrier@fawiki Cspurrier@itwiki Cspurrier@metawiki Cspurrier@ptwiki Cspurrier@simplewiki Cspurrier@ukwiki Cspurrier@zhwiki Céréales Killer@frwiki DBarratt (WMF)@testwiki DGG@enwiki DHN@viwiki DJackson (WMF)@testwiki DMaza (WMF)@testwiki DR@ruwiki DWalden (WMF)@frwiki DWalden (WMF)@testwiki Daedalus@svwiki Daimore@ptwiki Damzow@hewiki Dan Koehl@specieswiki Daniel Quinlan@enwiki Daniel@enwiki Daniuu@arwiki Daniuu@loginwiki Daniuu@metawiki Daniuu@nlwiki Daniuu@testwiki Danmichaelo@nowiki DannyH (WMF)@mediawikiwiki DannyH (WMF)@testwiki DannyS712@testwiki Danu Widjajanto@idwiki Darkoneko@aswiki Darkoneko@brwiki Darkoneko@bugwiki Darkoneko@cswiki Darkoneko@cswikisource Darkoneko@cswiktionary Darkoneko@dawiki Darkoneko@enwiki Darkoneko@enwikiversity Darkoneko@eswiki Darkoneko@fawiki Darkoneko@frwiki Darkoneko@frwikiquote Darkoneko@glwiki Darkoneko@huwiki Darkoneko@jawiktionary Darkoneko@mediawikiwiki Darkoneko@metawiki Darkoneko@mlwiki Darkoneko@newiki Darkoneko@nlwikiquote Darkoneko@ruwiktionary Darkoneko@skwiki Darkoneko@skwikisource Darkoneko@sourceswiki Darkoneko@srwiki Darkoneko@stwiki Darkoneko@testwiki Darkoneko@trwiki Darkoneko@ukwiki Darkoneko@viwiki Darkoneko@warwiki Darkoneko@wawiki Darkoneko@wowiki Darkoneko@yiwiki DatGuy@enwiki Datrio@fawiki David Fuchs@enwiki David Gerard@enwiki David Wadie Fisher-Freberg@idwiki Dbeef@enwiki Dbl2010@azwiki Dbl2010@kowiktionary Dbl2010@testwiki Dbl2010@trwiki Dbl2010@vlswiki Dcastor@svwiki Deineka@ukwiki Demicx@bswiki Der-Wir-Ing@dewiki DerHexer@loginwiki DerHexer@metawiki Derbeth@enwikibooks Deror avi@hewiki Deskana (WMF)@enwiki Deskana (WMF)@mediawikiwiki Deskana@enwiki Dferg@acewiki Dferg@bat_smgwiki Dferg@bewiki Dferg@bgwiki Dferg@brwiki Dferg@cebwiki Dferg@enwikiversity Dferg@eowiki Dferg@extwiki Dferg@fawiki Dferg@frwikiversity Dferg@gdwiki Dferg@glwiktionary Dferg@hawwiki Dferg@hiwiki Dferg@hywiki Dferg@iswiki Dferg@itwikiquote Dferg@jawikiquote Dferg@kawiki Dferg@kowiki Dferg@kshwiki Dferg@lawiki Dferg@lvwiki Dferg@mkwiktionary Dferg@mswiki Dferg@ndswiki Dferg@orwiktionary Dferg@pmswiki Dferg@ptwikibooks Dferg@ptwikisource Dferg@ptwiktionary Dferg@quwiki Dferg@rowiki Dferg@siwiki Dferg@specieswiki Dferg@sqwiki Dferg@swwiki Dferg@tewiki Dferg@ukwiki Dferg@uzwiki Dferg@viwiki Dferg@vowiki Dferg@wuuwiki Dferg@xalwiki Dferg@xhwiki Dferg@zh_classicalwiki Dferg@zh_yuewiki Dferg@zhwikinews Djordjes@srwiki Djsasso@simplewiki Dmcdevit@wikimania2008wiki Dmitry Gerasimov@ruwiki Dmthoth@kowiki DoRD@enwiki Dominic@enwiki Dominic@enwiktionary Doug Weller@enwiki Doğu@commonswiki Doğu@itwiki Dr-Taher@arwiki Dragoniez@jawiki Drahreg01@dewiki Drbug@ruwiki Dreamy Jazz@enwiki Dreamy Jazz@testwiki Drini@aawiki Drini@afwiki Drini@anwiki Drini@astwiki Drini@azwiki Drini@bgwiki Drini@brwiki Drini@cywiki Drini@dawikibooks Drini@dsbwiki Drini@enwikibooks Drini@enwikiquote Drini@enwikisource Drini@enwikiversity Drini@eowiki Drini@eswiki Drini@etwiki Drini@euwiktionary Drini@extwiki Drini@fawiki Drini@fiwiki Drini@fiwikibooks Drini@fiwikinews Drini@fiwikisource Drini@frpwiki Drini@frwikiquote Drini@gawiki Drini@glwiki Drini@hakwiki Drini@hiwiki Drini@idwiki Drini@iewiki Drini@igwiki Drini@incubatorwiki Drini@iswiki Drini@iswikibooks Drini@itwikisource Drini@itwikisource Drini@jvwiki Drini@kmwiki Drini@kowikiquote Drini@lawiki Drini@lbewiki Drini@lgwiki Drini@lmowiki Drini@lowiki Drini@lvwiki Drini@mediawikiwiki Drini@mlwiki Drini@mswiki Drini@ngwiki Drini@nlwikiquote Drini@nowiki Drini@nowikisource Drini@nvwiki Drini@piwiki Drini@rnwiki Drini@rowiki Drini@ruwiki Drini@ruwikinews Drini@ruwikiquote Drini@scnwiktionary Drini@sgwiki Drini@shwiki Drini@skwiki Drini@snwiki Drini@sowiki Drini@sqwiki Drini@srwiki Drini@swwiki Drini@testwiki Drini@tlwiki Drini@towiki Drini@ukwiki Drini@ukwikinews Drini@ukwiktionary Drini@viwiki Drini@wuuwiki Drini@xalwiki Drini@xhwiki Drini@yiwiki Drini@zh_yuewiki Drini@zhwiki Drmies@enwiki Dtom@hrwiki Dungodung@metawiki Dungodung@srwiki Dungodung@testwiki Durifon@frwiki Dyolf77@arwiki Dyolf77@frwiki Dzordzm@srwiki E.coli@hrwiki EMill-WMF@enwiki EPIC@enwiki EPIC@loginwiki EPIC@metawiki EVula@enwikiquote EdJohnston@enwiki Edmenb@eswiki Effeietsanders@dewiktionary Effeietsanders@enwikiquote Effeietsanders@enwikiversity Effeietsanders@fawiki Effeietsanders@kowiki Effeietsanders@kshwiki Effeietsanders@kuwiki Effeietsanders@nowiki Effeietsanders@ptwiki Effeietsanders@testwiki Einsbor@enwiki Einsbor@loginwiki Ejs-80@fiwiki Elcobbola@commonswiki Eldarion@trwiki Elen of the Roads@enwiki Elfix@cywiktionary Elfix@dewikibooks Elfix@frwiki Elfix@idwikiquote Elfix@itwikinews Elfix@loginwiki Elfix@mywiki Elfix@ptwikibooks Elfix@ptwikiquote Elfix@ptwikisource Elian@dewiki Elli@enwiki Ellywa@nlwiki Elmacenderesi@trwiki Elockid@enwiki Elph@arwiki Elton@astwiki Elton@cebwiki Elton@chwiki Elton@incubatorwiki Elton@jawikinews Elton@loginwiki Elton@mediawikiwiki Elton@plwikiquote Elton@ptwikiquote Elton@shwiki Elton@testwiki Elton@wikidatawiki Elton@zhwiktionary Elwood@itwiki Emir Kotromanić@bswiki Emufarmers@enwiki Emufarmers@eswiki Emufarmers@metawiki Emx@bswiki Enterprisey@enwiki Epinheiro@ptwiki Eptalon@simplewiki Erkan Yilmaz@enwikiversity Erwin85@zhwiki Erwin@nlwiki Essjay@enwiki Eta Carinae@ptwiki Euryalus@enwiki Eusebius@commonswiki EvgenyGenkin@ruwiki Ex13@hrwiki FT2@enwiki Faendalimas@specieswiki Faso@jawiki FayssalF@enwiki Felipe da Fonseca@ptwiki Ferret@enwiki Filzstift@dewiki Firefly@enwiki Fitindia@commonswiki FloNight@enwiki Fluff@svwiki FoBe@huwiki Fr33kman@simplewiki Frank@enwiki Fred Bauder@enwiki Freetrashbox@jawiki Fritzpoll@enwiki Gac@itwiki Galahad@eswiki Galahad@fawiki Galahad@trwiki Gamaliel@enwiki GeneralNotability@enwiki Geonuch@thwiki Giraffer@enwiki Girth Summit@enwiki Gmaxwell@commonswiki Gnom@dewiki Goo3@ukwiki GorillaWarfare@enwiki Gpvos@nlwiki Gribeco@frwiki Grin@huwiki Groucho NL@nlwiki Guerillero@enwiki Guillom@arwiki Guillom@cvwiki Guillom@dawiki Guillom@fawiki Guillom@fiwikiquote Guillom@foundationwiki Guillom@frwiki Guillom@frwikinews Guillom@frwikisource Guillom@frwiktionary Guillom@iawiki Guillom@ilowiki Guillom@iowiki Guillom@iswiki Guillom@itwikinews Guillom@kabwiki Guillom@kowiki Guillom@rowiki Guillom@siwiktionary Guillom@trwiki Gutza@rowiki Gvf@itwiki Góngora@cawiki HJ Mitchell@enwiki HVL@ptwiki Ha98574@kowiki HaeB@dewiki HaithamS (WMF)@enwiki HakanIST@loginwiki Hardscarf@nlwiki Harel@hewiki Haros@nowiki Harriv@fiwiki Hasley@barwiki Hasley@loginwiki Hasley@metawiki Hayabusa future@idwiki Hei ber@dewiki Hei ber@enwiki Hephaion@dewiki Herbythyme@commonswiki Herbythyme@enwikibooks Herbythyme@metawiki Herbythyme@nowiki Hersfold@enwiki Hexasoft@frwiki Hidayatsrf@idwiki Hidro@hewiki Hispa@eswiki Hjvannes@nlwiki Hoo man@loginwiki HouseBlaster@enwiki Huji@fawiki Hunyadym@huwiki Hyméros@frwiki INeverCry@commonswiki IRTC1015@kowiki Ilya Voyager@ruwiki IlyaHaykinson@enwikinews Indech@ptwiki Infinite0694@eswiki Infinite0694@jawiki Infinite0694@loginwiki Infinite0694@metawiki Iridescent@enwiki IvanLanin@idwiki Ivanvector@enwiki Izno@enwiki J.delanoy@enwiki JAbrams (WMF)@cebwiki JAbrams (WMF)@commonswiki JAbrams (WMF)@cswiktionary JAbrams (WMF)@enwiki JAbrams (WMF)@eowiki JAbrams (WMF)@frwiki JAbrams (WMF)@jawiki JAbrams (WMF)@metawiki JAbrams (WMF)@ptwiki JAbrams (WMF)@wikidatawiki JCrespo (WMF)@labswiki JEissfeldt (WMF)@dewiki JEissfeldt (WMF)@enwiki JEissfeldt (WMF)@jawiki JJMC89@eswiki JJMC89@loginwiki JSutherland (WMF)@afwiki JSutherland (WMF)@arwiki JSutherland (WMF)@arwikisource JSutherland (WMF)@azwiki JSutherland (WMF)@bgwiki JSutherland (WMF)@commonswiki JSutherland (WMF)@cswiki JSutherland (WMF)@dawiki JSutherland (WMF)@dewiki JSutherland (WMF)@enwiki JSutherland (WMF)@enwikibooks JSutherland (WMF)@enwikiquote JSutherland (WMF)@enwiktionary JSutherland (WMF)@eswiki JSutherland (WMF)@fawiki JSutherland (WMF)@frwiki JSutherland (WMF)@frwikiquote JSutherland (WMF)@frwikiversity JSutherland (WMF)@hewiki JSutherland (WMF)@hrwiki JSutherland (WMF)@huwiki JSutherland (WMF)@hywiki JSutherland (WMF)@idwiki JSutherland (WMF)@itwiki JSutherland (WMF)@jawiki JSutherland (WMF)@kowiki JSutherland (WMF)@loginwiki JSutherland (WMF)@mediawikiwiki JSutherland (WMF)@metawiki JSutherland (WMF)@nlwiki JSutherland (WMF)@nowiki JSutherland (WMF)@plwiki JSutherland (WMF)@ptwiki JSutherland (WMF)@ruwiki JSutherland (WMF)@simplewiki JSutherland (WMF)@svwiki JSutherland (WMF)@testwiki JSutherland (WMF)@ukwiki JSutherland (WMF)@wikidatawiki JSutherland (WMF)@zhwiki JWSchmidt@enwikiversity JWang (WMF)@testwiki Jafeluv@fiwiki Jagro@cswiki Jake Park@eswiki Jalexander-WMF@arwiki Jalexander-WMF@azwiki Jalexander-WMF@bgwiki Jalexander-WMF@cawiki Jalexander-WMF@commonswiki Jalexander-WMF@cswiki Jalexander-WMF@dawiki Jalexander-WMF@dewiki Jalexander-WMF@enwiki Jalexander-WMF@enwikibooks Jalexander-WMF@enwikiquote Jalexander-WMF@enwikisource Jalexander-WMF@enwikiversity Jalexander-WMF@enwikivoyage Jalexander-WMF@enwiktionary Jalexander-WMF@eswiki Jalexander-WMF@eswikivoyage Jalexander-WMF@frwiki Jalexander-WMF@gawiki Jalexander-WMF@hewiki Jalexander-WMF@hewikisource Jalexander-WMF@incubatorwiki Jalexander-WMF@iswiki Jalexander-WMF@itwiki Jalexander-WMF@kowiki Jalexander-WMF@kowikibooks Jalexander-WMF@kowikisource Jalexander-WMF@loginwiki Jalexander-WMF@mediawikiwiki Jalexander-WMF@metawiki Jalexander-WMF@mywiki Jalexander-WMF@nowiki Jalexander-WMF@nowiktionary Jalexander-WMF@plwiki Jalexander-WMF@ptwiki Jalexander-WMF@ruwiki Jalexander-WMF@ruwikibooks Jalexander-WMF@ruwikisource Jalexander-WMF@simplewiki Jalexander-WMF@specieswiki Jalexander-WMF@svwiki Jalexander-WMF@testwiki Jalexander-WMF@wikidatawiki Jalexander-WMF@zhwiki Jalexander@foundationwiki Jalexander@labswiki James LL@loginwiki Jameslwoodward@commonswiki Jamesofur@simplewiki Jamie Tubers@enwiki Janorovic Volkov@idwiki Japiot@nlwiki Jasper Deng@wikidatawiki Jayjg@enwiki Jbribeiro1@ptwiki Jcb@nlwiki Jclemens@enwiki Jdforrester (WMF)@enwiki Jdforrester (WMF)@enwikiquote Jdforrester@enwiki Jean-Christophe BENOIST@frwiki Jeffq@enwikiquote Jimbo Wales@enwiki Jimmy Xu@zhwiki Jmrebes@cawiki Jniemenmaa@fiwiki Joe Roe@enwiki Johannnes89@loginwiki Johannnes89@metawiki John Vandenberg@enwiki John Vandenberg@enwikisource Jon Harald Søby@akwiki Jon Harald Søby@alswiki Jon Harald Søby@cawiki Jon Harald Søby@cswiki Jon Harald Søby@dewiki Jon Harald Søby@enwiki Jon Harald Søby@enwikiquote Jon Harald Søby@fawiki Jon Harald Søby@fiwikibooks Jon Harald Søby@fiwikisource Jon Harald Søby@frwiki Jon Harald Søby@iswiki Jon Harald Søby@itwiki Jon Harald Søby@kowiki Jon Harald Søby@metawiki Jon Harald Søby@nnwiki Jon Harald Søby@nowiki Jon Harald Søby@nowikibooks Jon Harald Søby@plwikibooks Jon Harald Søby@plwikinews Jon Harald Søby@plwiktionary Jon Harald Søby@ruwiki Jon Harald Søby@skwiki Jon Harald Søby@svwiki Jon Harald Søby@svwikinews Jon Harald Søby@testwiki Jon Harald Søby@trwiki Jon Harald Søby@yiwiki Jon Kolbert@loginwiki Joutbis@cawiki Jpgordon@enwiki Jrogers (WMF)@metawiki Julle@svwiki Julle@testwiki Just Sayori@thwiki JusteJuju10@frwiki Jyothis@eswiktionary Jyothis@loginwiki Jyothis@metawiki KColeman-WMF@testwiki KHarlan (WMF)@loginwiki KHarlan (WMF)@testwiki KLevan (WMF)@arwiki KLevan (WMF)@commonswiki KLevan (WMF)@enwiki KLevan (WMF)@hewiki KLevan (WMF)@idwiki KLevan (WMF)@metawiki KMT@jawiki KRLS@cawiki Kaare@dawiki Kal-El~bswiki@bswiki Kalliope (WMF)@azwiki Kalliope (WMF)@commonswiki Kalliope (WMF)@dewiki Kalliope (WMF)@enwiki Kalliope (WMF)@enwikiversity Kalliope (WMF)@enwiktionary Kalliope (WMF)@eswiki Kalliope (WMF)@frwiki Kalliope (WMF)@frwiktionary Kalliope (WMF)@idwiki Kalliope (WMF)@itwiki Kalliope (WMF)@jawiki Kalliope (WMF)@loginwiki Kalliope (WMF)@metawiki Kalliope (WMF)@outreachwiki Kalliope (WMF)@testwiki Kalliope (WMF)@zhwiki Kanjy@jawiki Karol007@plwiki Karsten11@dewiki Kbrown (WMF)@arwiki Kbrown (WMF)@commonswiki Kbrown (WMF)@dewiki Kbrown (WMF)@enwiki Kbrown (WMF)@enwikibooks Kbrown (WMF)@enwikiversity Kbrown (WMF)@enwikivoyage Kbrown (WMF)@eowiki Kbrown (WMF)@eswiki Kbrown (WMF)@eswikibooks Kbrown (WMF)@frwiki Kbrown (WMF)@frwiktionary Kbrown (WMF)@itwiki Kbrown (WMF)@loginwiki Kbrown (WMF)@metawiki Kbrown (WMF)@ptwiki Kbrown (WMF)@simplewiki Kbrown (WMF)@testwiki Kbrown (WMF)@trwiki Kbrown (WMF)@zhwiki Keegan@enwiki Kegns@zhwiki Kelapstick@enwiki Kiran Gopi@mlwiki Kirill Lokshin@enwiki KnightLago@enwiki KnudW@dawiki Koavf@specieswiki KonstantinaG07@loginwiki KovacsUr@huwiki KrakatoaKatie@enwiki Krd@commonswiki Krd@loginwiki Krd@metawiki Ks aka 98@jawiki Ks0stm@enwiki Kulac@dewiki Kv75@ruwiki Kylu@enwiki Kylu@metawiki L235@enwiki LD@frwiki LFaraone@enwiki Laaknor@metawiki Laaknor@nowiki Ladsgroup@fawiki Ladsgroup@labswiki Ladsgroup@testwiki Ladypine@hewiki Lankiveil@enwiki Lanwi1@zhwiki Lar@alswiki Lar@anwiki Lar@arwiki Lar@bgwiki Lar@commonswiki Lar@dewiki Lar@enwiki Lar@enwikiquote Lar@enwikisource Lar@enwikiversity Lar@fawiki Lar@frwiki Lar@hywiki Lar@kywiktionary Lar@metawiki Lar@mswiki Lar@nlwiki Lar@nlwikiquote Lar@plwiki Lar@rowiki Lar@scowiki Lar@simplewiki Lar@simplewiktionary Lar@thwiki Lar@tiwiki Lar@trwiki Lar@ukwiki Lar@viwiki Lar@zhwiki Le chat perché@frwiki Lechatjaune@ptwiki Leinad pl@rowiki Leinad@metawiki Leinad@plwiki Lewisiscrazy@frwiki Lijealso@ptwiki Linedwell@frwiki Linedwell@loginwiki Liso@skwiki Little Sunshine@ptwiki Lofty abyss@abwiki Lofty abyss@acewiki Lofty abyss@astwiki Lofty abyss@bclwiki Lofty abyss@betawikiversity Lofty abyss@bnwiki Lofty abyss@cawiktionary Lofty abyss@chrwiki Lofty abyss@crwiki Lofty abyss@cswikibooks Lofty abyss@cswiktionary Lofty abyss@dawikisource Lofty abyss@dawiktionary Lofty abyss@dewikibooks Lofty abyss@dewikinews Lofty abyss@dewiktionary Lofty abyss@eewiki Lofty abyss@enwikibooks Lofty abyss@enwikiversity Lofty abyss@enwikivoyage Lofty abyss@etwiki Lofty abyss@fawiki Lofty abyss@glwiki Lofty abyss@huwiktionary Lofty abyss@idwikibooks Lofty abyss@idwikiquote Lofty abyss@idwikisource Lofty abyss@idwiktionary Lofty abyss@iewiki Lofty abyss@ilowiki Lofty abyss@incubatorwiki Lofty abyss@itwikisource Lofty abyss@itwikisource Lofty abyss@jvwiki Lofty abyss@kmwiki Lofty abyss@kowikiquote Lofty abyss@lawiki Lofty abyss@lbewiki Lofty abyss@lgwiki Lofty abyss@lmowiki Lofty abyss@lowiki Lofty abyss@lvwiki Lofty abyss@mediawikiwiki Lofty abyss@metawiki Lofty abyss@mnwiki Lofty abyss@mswiki Lofty abyss@ndswiki Lofty abyss@nlwikibooks Lofty abyss@nlwikiquote Lofty abyss@nlwiktionary Lofty abyss@nowiki Lofty abyss@nycwikimedia Lofty abyss@nywiki Lofty abyss@omwiki Lofty abyss@outreachwiki Lofty abyss@papwiki Lofty abyss@pflwiki Lofty abyss@pihwiki Lofty abyss@plwikiquote Lofty abyss@plwikisource Lofty abyss@ptwikibooks Lofty abyss@ptwikinews Lofty abyss@ptwikiquote Lofty abyss@ptwikiversity Lofty abyss@ptwikivoyage Lofty abyss@ptwiktionary Lofty abyss@quwiki Lofty abyss@simplewiki Lofty abyss@simplewiktionary Lofty abyss@sourceswiki Lofty abyss@specieswiki Lofty abyss@suwiki Lofty abyss@svwikinews Lofty abyss@svwikiquote Lofty abyss@swwiki Lofty abyss@tiwiktionary Lofty abyss@trwikisource Lofty abyss@vewiki Lofty abyss@warwiki Lofty abyss@wikidatawiki Lofty abyss@wuuwiki Lofty abyss@yiwiki Lofty abyss@yowiki Lofty abyss@zh_classicalwiki Lord Mota@ptwiki Luk@enwiki Luna Santin@enwiki Lusitana@ptwiki Lusum@itwiki Lymantria@commonswiki Lymantria@wikidatawiki Lzur@plwiki M7@kawiki M7@loginwiki M7@metawiki M7@simplewiki MAna (WMF)@testwiki MBisanz@enwiki MBq@dewiki MF-Warburg@loginwiki MFischer (WMF)@enwiki MGA73@dawiki MGodwin@enwiki MPelletier (WMF)@enwiki MPelletier (WMF)@wikidatawiki MPinchuk (WMF) (usurped)@mediawikiwiki MPostoronca-WMF@testwiki MSzabo-WMF@testwiki MZaplotnik@slwiki Mackensen@enwiki Madaki@itwiki Magister Mathematicae@akwiki Magister Mathematicae@alswiki Magister Mathematicae@angwiki Magister Mathematicae@arwiki Magister Mathematicae@aswiki Magister Mathematicae@azwiktionary Magister Mathematicae@barwiki Magister Mathematicae@bawiki Magister Mathematicae@bpywiki Magister Mathematicae@bugwiki Magister Mathematicae@cawiki Magister Mathematicae@chwiki Magister Mathematicae@commonswiki Magister Mathematicae@cowiki Magister Mathematicae@cswiki Magister Mathematicae@enwiki Magister Mathematicae@enwikinews Magister Mathematicae@eswiki Magister Mathematicae@eswikibooks Magister Mathematicae@eswikinews Magister Mathematicae@eswikiquote Magister Mathematicae@eswikisource Magister Mathematicae@eswikiversity Magister Mathematicae@fawiki Magister Mathematicae@ffwiki Magister Mathematicae@fiwikiquote Magister Mathematicae@fjwiki Magister Mathematicae@gawiktionary Magister Mathematicae@gdwiki Magister Mathematicae@itwikibooks Magister Mathematicae@kabwiki Magister Mathematicae@kowiki Magister Mathematicae@kwwiktionary Magister Mathematicae@metawiki Magister Mathematicae@nahwiki Magister Mathematicae@pihwiki Magister Mathematicae@ptwiki Magister Mathematicae@quwiki Magister Mathematicae@simplewiki Magister Mathematicae@simplewiktionary Magister Mathematicae@specieswiki Magister Mathematicae@stwiki Magister Mathematicae@tswiki Magister Mathematicae@ugwiki Magister Mathematicae@uzwiki Magister Mathematicae@zawiki MagnusA@svwiki Magog the Ogre@commonswiki Mailer diablo@enwiki Majorly@simplewiki Malatinszky@huwiki Marc Mongenet@frwiki MarcGarver@astwiki MarcGarver@azwikibooks MarcGarver@bxrwiki MarcGarver@cewiki MarcGarver@chrwiktionary MarcGarver@crwiki MarcGarver@cswikibooks MarcGarver@cswikiquote MarcGarver@enwikibooks MarcGarver@enwikiquote MarcGarver@enwikiversity MarcGarver@enwikivoyage MarcGarver@eswikiquote MarcGarver@fawiki MarcGarver@hawiki MarcGarver@hewikivoyage MarcGarver@hiwiki MarcGarver@idwikiquote MarcGarver@idwikisource MarcGarver@incubatorwiki MarcGarver@kabwiki MarcGarver@loginwiki MarcGarver@ltwiktionary MarcGarver@mediawikiwiki MarcGarver@mswiki MarcGarver@nlwikimedia MarcGarver@nlwikivoyage MarcGarver@nowiki MarcGarver@piwiki MarcGarver@ptwikivoyage MarcGarver@scowiki MarcGarver@slwiki MarcGarver@svwikivoyage MarcGarver@wuuwiki MarcGarver@zhwiki Marcelo Victor@ptwiki MarcoAurelio@astwiki MarcoAurelio@barwiki MarcoAurelio@chrwiki MarcoAurelio@eewiki MarcoAurelio@enwiki MarcoAurelio@eswikiquote MarcoAurelio@euwiki MarcoAurelio@glwiki MarcoAurelio@loginwiki MarcoAurelio@mediawikiwiki MarcoAurelio@metawiki MarcoAurelio@nowiki MarcoAurelio@ptwiki MarcoAurelio@ptwikiquote MarcoAurelio@sourceswiki MarcoAurelio@testwiki MarcoAurelio@zhwiki Mardetanha@commonswiki Mardetanha@enwiki Mardetanha@loginwiki Mardetanha@metawiki Marine-Blue@jawiki Mark Bergsma@enwiki Mark Bergsma@nlwiki Markov@frwiki Martin H.@commonswiki Martin Urbanec (WMF)@cswiki Martin Urbanec (WMF)@cswikiversity Martin Urbanec (WMF)@loginwiki Martin Urbanec (WMF)@metawiki Martin Urbanec (WMF)@testwiki Martin Urbanec@cswiki Martin Urbanec@enwiki Martin Urbanec@loginwiki Martin Urbanec@metawiki Martin Urbanec@testwiki Masti@loginwiki Masti@plwiki Matanya@enwiki Matanya@hewiki Matanya@testwiki Materialscientist@enwiki Mathis B@frwiki Mathonius@loginwiki Matiia@loginwiki MatthiasGor@plwiki Maurilbert@frwiki MaxSem@bgwiki MaxSem@elwiki MaxSem@enwiki MaxSem@enwikiquote MaxSem@enwikisource MaxSem@fowiktionary MaxSem@hrwiki MaxSem@metawiki MaxSem@nowiki MaxSem@nowikibooks MaxSem@plwiki MaxSem@shwiktionary MaxSem@simplewiki MaxSem@skwiki MaxSem@srwiki MaxSem@stwiki MaxSem@svwiki MaxSem@ukwiki MaxSem@yiwiki MaxSem@zhwiki Maxim@enwiki Mdennis (WMF)@commonswiki Mdennis (WMF)@dewiki Mdennis (WMF)@enwiki Mdennis (WMF)@enwikinews Mdennis (WMF)@enwikiquote Mdennis (WMF)@enwikisource Mdennis (WMF)@frwiki Mdennis (WMF)@frwiktionary Mdennis (WMF)@loginwiki Mdennis (WMF)@metawiki Mdennis (WMF)@simplewiki Mdennis (WMF)@svwiki MdsShakil@loginwiki MdsShakil@test2wiki MdsShakil@testwiki Melos@itwiki Melos@loginwiki Melos@sswiki Meno25@arwiki Mentisock@wikidatawiki Mercy@metawiki Meursault2004@idwiki Mido@arwiki Midom@enwiki Midom@metawiki Mike V@enwiki Mike.lifeguard@commonswiki Mike.lifeguard@enwikibooks Mike.lifeguard@metawiki Mike.lifeguard@pmswiki Mike.lifeguard@rmywiki Mike.lifeguard@tywiki MikkoM@fiwiki Millennium bug@ptwiki Millosh@azwiki Millosh@bswiki Millosh@cswiki Millosh@dewiktionary Millosh@enwiki Millosh@fawiki Millosh@gdwiktionary Millosh@hrwiki Millosh@kowiki Millosh@metawiki Millosh@pmswiki Millosh@ruwiki Millosh@ruwikisource Millosh@sqwiki Millosh@trwiki Millosh@zhwiki Minderbinder@dewiki Minorax@metawiki Mkdw@enwiki Mmenal@frwiki Moneytrees@enwiki Montgomery@eswiki Mormegil@cswiki Morven@enwiki MrJaroslavik@loginwiki MrJaroslavik@testwiki Msz2001@plwiki Mtarch11@itwiki Mtarch11@metawiki MuZemike@enwiki MusikAnimal@enwiki MusikAnimal@loginwiki Mwpnl@trwiki Mxn@viwiki Mykola7@loginwiki Mykola7@ukwiki Mys 721tx@zhwiki Mz7@enwiki NForrester (WMF)@bewiki NForrester (WMF)@bgwiki NForrester (WMF)@commonswiki NForrester (WMF)@enwiki NForrester (WMF)@eswiki NForrester (WMF)@metawiki NForrester (WMF)@ruwiki NForrester (WMF)@shwiki NForrester (WMF)@srwiki NForrester (WMF)@wikidatawiki NForrester (WMF)@zhwiki NKohli (WMF)@testwiki NNair (WMF)@enwiki NahidSultan (WMF)@arwiki NahidSultan (WMF)@arwikinews NahidSultan (WMF)@bnwiki NahidSultan (WMF)@bnwiktionary NahidSultan (WMF)@cawiki NahidSultan (WMF)@commonswiki NahidSultan (WMF)@enwiki NahidSultan (WMF)@enwikinews NahidSultan (WMF)@enwikiquote NahidSultan (WMF)@eswiki NahidSultan (WMF)@ffwiki NahidSultan (WMF)@foundationwiki NahidSultan (WMF)@frwiki NahidSultan (WMF)@hewiki NahidSultan (WMF)@jawiki NahidSultan (WMF)@jawiktionary NahidSultan (WMF)@knwiki NahidSultan (WMF)@kowiki NahidSultan (WMF)@loginwiki NahidSultan (WMF)@mediawikiwiki NahidSultan (WMF)@metawiki NahidSultan (WMF)@miwiki NahidSultan (WMF)@mkwiki NahidSultan (WMF)@mrwiki NahidSultan (WMF)@mswiki NahidSultan (WMF)@ptwiki NahidSultan (WMF)@rowiki NahidSultan (WMF)@ruwiki NahidSultan (WMF)@simplewiki NahidSultan (WMF)@specieswiki NahidSultan (WMF)@srwiki NahidSultan (WMF)@tawiki NahidSultan (WMF)@ukwiki NahidSultan (WMF)@uzwiki NahidSultan (WMF)@wikidatawiki NahidSultan (WMF)@zhwiki NahidSultan@bnwiki NahidSultan@loginwiki Nakor@frwiki Nataev@uzwiki NativeForeigner@enwiki Natuur12@nlwiki Nedops@plwiki Nelson Teixeira@ptwiki Nettadi@hewiki Neukoln@hewiki Newyorkbrad@enwiki Nick1915@bat_smgwiki Nick1915@elwiki Nick1915@fawiki Nick1915@itwikibooks Nick1915@itwiktionary Nick1915@kowiki Nick1915@kwwiki Nick1915@lmowiki Nick1915@metawiki Nick1915@ptwiki Nick1915@scnwiki Nick1915@trwiki Nick1915@zhwiki NickK@ukwiki NinjaRobotPirate@enwiki Nishkid64@enwiki NoFWDaddress@frwiki Nohirara@idwiki Noumenon@trwiki NuclearWarfare@enwiki Nuno Tavares@ptwiki Nux@plwiki Nyenyec@huwiki OJJ@cswiki Octahedron80@thwiki OhanaUnited@enwikivoyage OneLittleMouse@ruwiki OnlyJonny@ptwiki Opabinia regalis@enwiki Operator873@loginwiki Operator873@simplewiki Ori@hewiki Oscar@commonswiki Oscar@dewiki Oscar@enwiki Oscar@frwiki Oscar@metawiki Oscar@nlwiki Oscar@nlwikibooks Oscarami@eswiki Oshwah@enwiki PEarley (WMF)@enwiki PSaxena (WMF)@mediawikiwiki PSaxena (WMF)@testwiki Paginazero@commonswiki Paginazero@enwiki Paginazero@enwikiquote Paginazero@itwiki Paginazero@itwikibooks Paginazero@ruwiki Paginazero@yiwiki Pallerti@huwiki Palnatoke@dawiki Pap3rinik@itwiki Pathoschild@commonswiki Pathoschild@cywiki Pathoschild@elwiki Pathoschild@enwiki Pathoschild@enwikinews Pathoschild@enwikiquote Pathoschild@enwikisource Pathoschild@eowiki Pathoschild@eswiki Pathoschild@eswikiquote Pathoschild@etwiki Pathoschild@fawiki Pathoschild@fiwikinews Pathoschild@hewiki Pathoschild@hsbwiki Pathoschild@huwiki Pathoschild@idwiki Pathoschild@incubatorwiki Pathoschild@itwiki Pathoschild@itwikiquote Pathoschild@klwiki Pathoschild@kwwiktionary Pathoschild@lbwiki Pathoschild@metawiki Pathoschild@miwiki Pathoschild@mlwiki Pathoschild@nlwiki Pathoschild@nnwiki Pathoschild@ocwiki Pathoschild@ptwiki Pathoschild@ruwiktionary Pathoschild@simplewiki Pathoschild@siwiki Pathoschild@sqwiki Pathoschild@srwiki Pathoschild@urwiktionary Pathoschild@vewiki Pathoschild@yiwiki Pathoschild@zhwiki Pathoschild@zhwiktionary PatríciaR@ptwiki Penn Station@jawiki Perrak@dewiki Pete Forsyth (WMF)@enwiki PeterSymonds@metawiki Peterdownunder@simplewiki PhilKnight@enwiki Philippe (WMF)@azwiki Philippe (WMF)@commonswiki Philippe (WMF)@dewiki Philippe (WMF)@enwiki Philippe (WMF)@enwikibooks Philippe (WMF)@enwikinews Philippe (WMF)@enwikiquote Philippe (WMF)@enwikisource Philippe (WMF)@enwikiversity Philippe (WMF)@enwikivoyage Philippe (WMF)@eswiki Philippe (WMF)@fawiki Philippe (WMF)@frwiki Philippe (WMF)@frwiktionary Philippe (WMF)@itwiki Philippe (WMF)@loginwiki Philippe (WMF)@mediawikiwiki Philippe (WMF)@metawiki Philippe (WMF)@mgwiktionary Philippe (WMF)@nlwiki Philippe (WMF)@simplewiki Philippe (WMF)@wikidatawiki Philippe (WMF)@xhwiki Philippe (WMF)@zhwiki Philippe@enwiki Piku@frwiki Pmlineditor@loginwiki Polimerek@plwiki Polimerek@plwikimedia Ponyo@enwiki Postrach@cswiki Poudou!@frwiki Pouyana@fawiki Premeditated Chaos@enwiki Primefac@enwiki Prométhée@frwiki Ptjackyll@plwiki Pundit@plwiki PurpleBuffalo@hewiki Putnik@ruwiki Q-bit array@ruwiki Queix@frwiki RLuts@ukwiki RadiX@bhwiki RadiX@cswikisource RadiX@enwikiversity RadiX@hiwiki RadiX@loginwiki RadiX@pswiki RadiX@ptwiki RadiX@ptwikibooks RadiX@ptwikiquote RadiX@svwikiversity Rastrojo@eswiki Raul654@enwiki Rax@dewiki Razimantv@mlwiki Rdsmith4@arwiki Rdsmith4@biwiki Rdsmith4@etwiki Rdsmith4@fawiki Rdsmith4@gawiki Rdsmith4@huwiki Rdsmith4@idwiki Rdsmith4@jawiki Rdsmith4@kowiki Rdsmith4@lmowiki Rdsmith4@lvwiki Rdsmith4@metawiki Rdsmith4@newiktionary Rdsmith4@nrmwiki Rdsmith4@rowiki Rdsmith4@rowiktionary Rdsmith4@scowiki Rdsmith4@trwiki Rdsmith4@zhwiki ReAl@ukwiki Reaper Eternal@enwiki Redux@enwiki Reedy (WMF)@arwiki Reedy (WMF)@enwiki Reedy (WMF)@mediawikiwiki Reedy (WMF)@metawiki Reedy (WMF)@testwiki Reedy@commonswiki Reedy@enwiki Reedy@itwiki Reedy@labswiki Reedy@wikidatawiki Rei-artur@ptwiki Renamed user mou89p43twvqcvm8ut9w3@enwiki Revi C.@betawikiversity Revi C.@dewikiquote Revi C.@enwiki Revi C.@enwikinews Revi C.@enwikivoyage Revi C.@eswikinews Revi C.@kowiktionary Revi C.@loginwiki Revi C.@mediawikiwiki Revi C.@rowiki Revi C.@ruwikinews Revi C.@ruwikiquote Revi C.@smnwiki Revi C.@zh_classicalwiki Revi C.@zhwiki Rexcornot@frwiki Reza1615@fawiki RiazACU@bnwiki Richwales@enwiki Richwales@eswiki RickinBaltimore@enwiki Risker@enwiki Rlevse@enwiki RobLa-WMF@enwiki RobLa-WMF@metawiki RobLa-WMF@plwiki Rodasmith@enwiktionary Roger Davies@enwiki Rojelio@itwiki Romihaitza@simplewiki Romihaitza@yiwiki RoySmith@arwiki RoySmith@enwiki RoySmith@metawiki Rsocol@rowiki Rubin16@ruwiki Ruslik0@loginwiki Ruslik0@nlwikibooks Ruthven@itwiki Ruthven@testwiki Ruy Pugliesi@mediawikiwiki Rxy@loginwiki Rxy@wikidatawiki Ryan Kaldari (WMF)@commonswiki Ryan Kaldari (WMF)@enwiki Ryan Lane (WMF)@labswiki SB Johnny@commonswiki SB Johnny@enwikibooks SB Johnny@enwikiversity SBJohnny@enwikibooks SBassett (WMF)@enwiki SBassett (WMF)@mediawikiwiki SBassett (WMF)@metawiki SBassett (WMF)@testwiki SEPRodrigues@ptwiki SHB2000@enwikivoyage SHB2000@loginwiki SMP@cawiki SNg (WMF)@enwiki SNg (WMF)@metawiki SNg (WMF)@nlwiki SPoore (WMF)@enwiki SPoore (WMF)@hrwiki SPoore (WMF)@itwiki SPoore (WMF)@metawiki SPoore (WMF)@simplewiki SPoore (WMF)@testwiki SQL@enwiki SSandu-WMF@enwiki SSpalding (WMF)@enwiki ST47@enwiki ST47@testwiki Sadrettin@trwiki Sahehco@fawiki Sahehco@testwiki Sakretsu@itwiki Sakretsu@loginwiki Salvio giuliano@enwiki Sam Korn@enwiki Samuel (WMF)@enwiki Samuel (WMF)@eswiki Samuel (WMF)@frwiki Samuel (WMF)@iawiki Samuel (WMF)@metawiki Samuel (WMF)@testwiki Samuel (WMF)@ukwiki Saper@plwiki Savh@itwikibooks Savh@loginwiki Sbisolo@itwiki Schiste@frwiki Schlum@frwiki Schniggendiller@loginwiki ScottishFinnishRadish@enwiki Sdrqaz@enwiki SelfieCity@enwikivoyage Seraphimblade@enwiki Shanel@aawiki Shanel@arwiki Shanel@cswiki Shanel@cswikiquote Shanel@eewiki Shanel@elwikibooks Shanel@enwiki Shanel@enwikiquote Shanel@eswikisource Shanel@fawiki Shanel@frwikinews Shanel@frwiktionary Shanel@igwiki Shanel@itwikinews Shanel@jawikinews Shanel@kowiki Shanel@mlwiki Shanel@ndswiki Shanel@ptwiki Shanel@ptwikinews Shanel@ptwiktionary Shanel@simplewiktionary Shanel@skwiki Shanel@skwiktionary Shanel@tiwiki Shanel@ukwiki Shanel@vecwiki Shanel@yiwiki Shanel@zhwiki Shanmugamp7@enwiki Shanmugamp7@loginwiki Shell Kinney@enwiki Shivanarayana@itwiki Shizhao@betawikiversity Shizhao@commonswiki Shizhao@dewiki Shizhao@enwiki Shizhao@frwiki Shizhao@kowiki Shizhao@kowikisource Shizhao@metawiki Shizhao@ptwikiquote Shizhao@ruwiki Shizhao@viwiki Shizhao@zhwiki Siam2019@thwiki SilkTork@enwiki SilverLocust@enwiki Sir Lestaty de Lioncourt@ptwikinews Sir kiss@hewiki Sir48@commonswiki Sir48@dawiki SirFozzie@enwiki Sjoerddebruin@loginwiki Skenmy@enwikinews Slaporte (WMF)@commonswiki Slaporte (WMF)@enwiki Slaporte (WMF)@ptwiki Smooth O@bswiki Snowdog@itwiki SoWhy@enwiki Sotiale@enwiki Sotiale@kowiki Sotiale@loginwiki Sotiale@metawiki Sotiale@wikidatawiki Spacebirdy@anwiki Spacebirdy@cawiki Spacebirdy@metawiki Spacebirdy@testwiki Spangineer@enwikisource SpeedyGonsales@hrwiki Spicy@enwiki Squasher@dewiki Stanglavine@loginwiki Stanglavine@metawiki Stanglavine@ptwiki Stephen Bain@enwiki Steve Smith@enwiki Stigfinnare@svwiki Strainu@rowiki Stryn@fiwiki Stryn@loginwiki Stwalkerster@enwiki Suisui@jawiki Superpes15@enwiki Superpes15@enwikiquote Superpes15@fawiki Superpes15@itwiki Superpes15@itwikiversity Superpes15@loginwiki Superpes15@metawiki Superspritz@itwiki Surjection@enwiktionary Syp@huwiki Szwedzki@plwiki TBolliger (WMF)@testwiki THargrove (WMF)@plwiki Tarawneh@arwiki Taweetham@thwiki Tbone@fiwiki Tegel@enwiki Tegel@eswiktionary Tegel@loginwiki Tegel@metawiki Tegel@svwiki Teles@enwiki Teles@loginwiki Teles@metawiki Teles@ptwiki Tfinc@enwiki Tfinc@metawiki Tgr (WMF)@commonswiki Tgr (WMF)@enwiki Tgr (WMF)@loginwiki Tgr (WMF)@metawiki Tgr (WMF)@testwiki Tgr (WMF)@trwiki Tgr@huwiki Thatcher@enwiki The Epopt@enwiki The Rambling Man@simplewiki The Squirrel Conspiracy@commonswiki TheDaveRoss@enwiktionary TheStriker@hewiki Theghaz@dewiki Theleekycauldron@enwiki Thenub314@enwikibooks TheresNoTime@enwiki TheresNoTime@loginwiki TheresNoTime@simplewiki Thogo@afwiki Thogo@alswiki Thogo@arzwiki Thogo@azwiktionary Thogo@barwiki Thogo@bat_smgwiki Thogo@bawiki Thogo@bgwiki Thogo@biwiki Thogo@brwiki Thogo@diqwiki Thogo@eewiki Thogo@elwiktionary Thogo@emlwiki Thogo@enwikisource Thogo@enwikiversity Thogo@enwiktionary Thogo@eowiki Thogo@eswiktionary Thogo@euwiki Thogo@euwiktionary Thogo@fawiki Thogo@fowiktionary Thogo@frwikinews Thogo@frwikiquote Thogo@fywiki Thogo@gawiki Thogo@gdwiktionary Thogo@gvwiktionary Thogo@hiwiki Thogo@hrwiki Thogo@huwiki Thogo@hywiki Thogo@itwikiquote Thogo@itwiktionary Thogo@iuwiki Thogo@jawikinews Thogo@jawikisource Thogo@jawiktionary Thogo@kmwiki Thogo@kowiki Thogo@liwiki Thogo@mediawikiwiki Thogo@miwiki Thogo@ndswiki Thogo@nnwiki Thogo@nowiki Thogo@pamwiki Thogo@ptwikinews Thogo@quwiki Thogo@rowiki Thogo@simplewiktionary Thogo@siwiki Thogo@sourceswiki Thogo@sowiki Thogo@specieswiki Thogo@stqwiki Thogo@vecwiki Thogo@viwiki Thogo@vowiki Thogo@wawiki Thogo@xhwiki Thogo@yiwiki Thogo@zhwiki Thryduulf@enwiki Tim Starling (WMF)@dewiki Tim Starling (WMF)@enwiki Tim Starling (WMF)@mediawikiwiki Tim Starling (WMF)@ruwiki Tim Starling@enwiki Tim Starling@labswiki Tim Starling@metawiki Tim Starling@testwiki Timekeepertmk@thwiki Timotheus Canens@enwiki Tinz@dewiki Tiptoety@commonswiki Tiptoety@enwiki Tiptoety@metawiki Titore@itwiki Tks4Fish@enwiki Tks4Fish@loginwiki Tks4Fish@ptwiki Tnxman307@enwiki ToBeFree@enwiki Tom Morris@enwikinews Tomer T@hewiki TonyBallioni@enwiki Toto Azéro@frwiki Triglav@jawiki Trijnstel@commonswiki Trijnstel@enwiki Trijnstel@loginwiki Trijnstel@metawiki TunnelESON@enwikiquote TunnelESON@fawiki TunnelESON@frwiktionary TunnelESON@jawikibooks TunnelESON@jawiktionary TunnelESON@kowiki TunnelESON@kowikisource TunnelESON@metawiki TunnelESON@mlwiki TunnelESON@ptwiki TunnelESON@ruwiki TunnelESON@siwiki TunnelESON@thwiki TunnelESON@ukwiki TunnelESON@viwiki TunnelESON@zhwiki Tznkai@enwiki Ugur Basak@trwiki Uncitoyen@trwiki UninvitedCompany@enwiki Ustad abu gosok@idwiki VIGNERON@loginwiki VWalters-WMF@enwiki VWalters-WMF@testwiki Vanamonde93@enwiki Vanished user 5zariu3jisj0j4irj@enwiki Vassyana@enwiki Vermont@loginwiki Vermont@metawiki Vermont@simplewiki Versageek@enwiki Versageek@enwiktionary VictorAnyakin@ukwiki Vigorous action@jawiki Vincent Vega@trwiki Viniciusmc@ptwiki Vituzzu@enwiki Vituzzu@eswikiquote Vituzzu@itwiki Vituzzu@metawiki Vituzzu@ptwikibooks Vlad@rowiki Vodomar@hrwiki W.CC@jawiki WBrown (WMF)@test2wiki WBrown (WMF)@testcommonswiki WBrown (WMF)@testwiki WMFOffice@arwiki WMFOffice@commonswiki WMFOffice@enwiki WMFOffice@fawiki WMFOffice@frwiki WMFOffice@loginwiki WMFOffice@mediawikiwiki WMFOffice@metawiki WMFOffice@ruwiki WMFOffice@wikidatawiki Wagino 20100516@idwiki Walter@brwiki Walter@nlwiki Walter@nlwikibooks Walter@testwiki WarX@plwiki Wegge@dawiki Werdna@enwiki Werdna@enwikisource Werdna@mediawikiwiki Whiteknight@enwikibooks Wiki13@loginwiki Wikimol@cswiki Wikitanvir@bnwiki WilliamH@enwiki Wim b@loginwiki Wind@ruwiki Wizardman@enwiki Wojciech Pędzich@betawikiversity Wojciech Pędzich@commonswiki Wojciech Pędzich@dewiktionary Wojciech Pędzich@eewiki Wojciech Pędzich@enwikiquote Wojciech Pędzich@enwikiversity Wojciech Pędzich@fawiki Wojciech Pędzich@fiwikibooks Wojciech Pędzich@itwikinews Wojciech Pędzich@kowiki Wojciech Pędzich@ltwiktionary Wojciech Pędzich@nlwikimedia Wojciech Pędzich@nowiki Wojciech Pędzich@outreachwiki Wojciech Pędzich@plwiki Wojciech Pędzich@plwikibooks Wojciech Pędzich@plwikimedia Wojciech Pędzich@plwikinews Wojciech Pędzich@plwikiquote Wojciech Pędzich@plwiktionary Wojciech Pędzich@rowiki Wojciech Pędzich@simplewiktionary Wojciech Pędzich@szlwiki Wojciech Pędzich@ukwiki Wojciech Pędzich@xhwiki Worm That Turned@enwiki Wugapodes@enwiki Wulfson@ruwiki XXBlackburnXx@enwiki XXBlackburnXx@loginwiki XXBlackburnXx@nlwiki Xania@enwikibooks Xaosflux@loginwiki Xaosflux@metawiki Xeno@enwiki Y-dash@jawiki Yahya@loginwiki Yamla@enwiki Yann@arwiki Yann@commonswiki Yann@enwikisource Yann@frwikinews Yann@hiwiki Yann@itwikiversity Yann@metawiki Yann@sourceswiki Yann@ukwiki YellowMonkey@enwiki Yunshui@enwiki Z1720@enwiki ZExley (WMF)@enwiki ZSoo (WMF)@enwiki Zabe@enwiki Zabe@mediawikiwiki Zabe@test2wiki Zabe@testcommonswiki Zabe@testwiki Zafer@trwikimedia Zzuuzz@enwiki Érico@ptwiki Боки@srwiki Ле Лой@ruwiki Обрадовић Горан@srwiki Стефанко1982@ukwiki בריאן@hewiki דגש@hewiki דולב@hewiki דניאל ב.@hewiki יונה בנדלאק@hewiki ירון@hewiki מוטי@hewiki מתניה@zhwiki עוזי ו.@hewiki עידו@hewiki עli@hewiki רחל1@hewiki باسم@arwiki جار الله@arwiki صالح@arwiki علاء@arwiki علاء@enwiki علاء@loginwiki علاء@wikidatawiki فيصل@arwiki ميموني@arwiki আফতাবuজ্জামান@bnwiki さかおり@jawiki 柏尾菓子@jawiki 欅@jawiki 海獺@jawiki 이강철@kowiki `; // PASTE YOUR LIST HERE let siteMap = {}; // List of wikis where CheckUser is disabled const disabledCUWikis = [ 'aawikibooks', 'abwiktionary', 'advisorywiki', 'akwiktionary', 'angwikiquote', 'angwikisource', 'astwikibooks', 'astwikiquote', 'aswikibooks', 'aswiktionary', 'avwiktionary', 'aywikibooks', 'bhwiktionary', 'biwikibooks', 'biwiktionary', 'bmwikibooks', 'bmwikiquote', 'bmwiktionary', 'bowiktionary', 'chwikibooks', 'chwiktionary', 'cnwikimedia', 'crwikiquote', 'crwiktionary', 'dzwiktionary', 'gawikibooks', 'gnwikibooks', 'gotwikibooks', 'guwikibooks', 'htwikisource', 'huwikinews', 'hzwiki', 'iewikibooks', 'iiwiki', 'internalwiki', 'kjwiki', 'kkwikiquote', 'knwikibooks', 'krwiki', 'krwikiquote', 'kswikibooks', 'kswikiquote', 'kwwikiquote', 'lbwikibooks', 'lbwikiquote', 'lnwikibooks', 'lvwikibooks', 'mhwiktionary', 'mnwikibooks', 'muswiki', 'nahwikibooks', 'nawikibooks', 'nawikiquote', 'ndswikibooks', 'ndswikiquote', 'piwiktionary', 'pswikibooks', 'quwikibooks', 'quwikiquote', 'rmwikibooks', 'rmwiktionary', 'rnwiktionary', 'scwiktionary', 'searchcomwiki', 'snwiktionary', 'spcomwiki', 'swwikibooks', 'thwikinews', 'tkwikibooks', 'tkwikiquote', 'towiktionary', 'transitionteamwiki', 'trwikinews', 'ttwikiquote', 'twwiktionary', 'ugwikibooks', 'ugwikiquote', 'vowikibooks', 'vowikiquote', 'wawikibooks', 'wikimania2005wiki', 'wikimania2006wiki', 'wikimania2007wiki', 'wikimania2009wiki', 'wikimania2015wiki', 'wowikiquote', 'xhwikibooks', 'xhwiktionary', 'yowikibooks', 'yowiktionary', 'zawikibooks', 'zawikiquote', 'zawiktionary', 'zh_min_nanwikibooks', 'zuwikibooks', 'aawiktionary', 'akwikibooks', 'amwikiquote', 'angwikibooks', 'bgwikinews', 'bowikibooks', 'chowiki', 'cowikibooks', 'cowikiquote', 'gawikiquote', 'howiki', 'ikwiktionary', 'kywikibooks', 'mhwiki', 'miwikibooks', 'mywikibooks', 'nawiki', 'nzwikimedia', 'pa_uswikimedia', 'qualitywiki', 'ruwikimedia', 'sdwikinews', 'sewikibooks', 'simplewikibooks', 'strategywiki', 'suwikibooks', 'tenwiki', 'usabilitywiki', 'uzwikibooks', 'wikimania2010wiki', 'wikimania2011wiki', 'wikimania2012wiki', 'wikimania2013wiki', 'wikimania2014wiki', 'wikimania2016wiki', 'wikimania2017wiki', 'wikimania2018wiki', 'zh_min_nanwikiquote' ]; async function loadSiteMatrix() { const api = new mw.Api(); try { const data = await api.get({ action: 'sitematrix', format: 'json', smtype: 'language|special', smlangprop: 'site', smsiteprop: 'dbname|url', formatversion: 2, origin: '*' }); const matrix = data.sitematrix; Object.keys(matrix).forEach(key => { const entry = matrix[key]; if (entry.site && Array.isArray(entry.site)) { entry.site.forEach(site => { siteMap[site.dbname] = site.url.replace(/^https?:\/\//, ''); }); } }); if (matrix.specials) { matrix.specials.forEach(special => { siteMap[special.dbname] = special.url.replace(/^https?:\/\//, ''); }); } return true; } catch (e) { return false; } } function initInterface() { document.title = "GCUS Data Generator"; $('#mw-content-text').empty().append(` <div style="background:white; border:1px solid #a2a9b1; padding:20px; font-family:sans-serif; color:#202122;"> <h1 style="margin-top:0; border-bottom:1px solid #a2a9b1;">GCUS Data Generator v7.0</h1> <p>Engine: <b>GlobalCheckUserStats (GCUS) Integrated</b>. <br> Format: <b>YYYY-MM-DD</b>. Filter: <b>CU < 60 days ignored</b>.</p> <div style="display:flex; gap:15px; margin-bottom:20px;"> <div style="flex:1;"> <strong>Input List (name@dbname):</strong> <textarea id="gcusInput" style="width:100%; height:180px; font-family:monospace; font-size:12px; border:1px solid #a2a9b1; margin-top:5px;">${USERS_LIST}</textarea> </div> <div style="flex:1;"> <strong>Log:</strong> <div id="gcusLog" style="height:180px; overflow-y:scroll; background:black; color:#0f0; font-family:monospace; font-size:11px; padding:5px; border-radius:3px; margin-top:5px;">Ready...</div> </div> <div style="width:220px;"> <strong>Skipped/Failed:</strong> <textarea id="gcusFailed" readonly style="width:100%; height:180px; font-family:monospace; font-size:11px; background:#fff0f0; border:1px solid #fcc; margin-top:5px; color:#a00;"></textarea> </div> </div> <button id="gcusStart" style="padding:10px 24px; background:#36c; color:white; border:none; cursor:pointer; font-weight:bold; border-radius:2px;">START SCAN</button> <span id="gcusStatus" style="margin-left:15px; font-weight:bold;"></span> <div id="gcusResultArea" style="display:none; margin-top:20px;"> <strong>Lua Output (Module:GlobalRoles/Data):</strong> <textarea id="gcusOutput" style="width:100%; height:350px; font-family:monospace; font-size:12px; background:#fffbe6; border:1px solid #f2e394; margin-top:5px;"></textarea> </div> </div> `); $('#gcusStart').on('click', runGenerator); } async function runGenerator() { const $btn = $('#gcusStart'), $log = $('#gcusLog'), $status = $('#gcusStatus'), $failed = $('#gcusFailed'); const input = $('#gcusInput').val(); $btn.prop('disabled', true).css('background', '#a2a9b1').text('PREPARING...'); $log.empty(); $failed.val(''); const log = (msg) => { $log.append($('<div>').text(`[${new Date().toLocaleTimeString()}] ${msg}`)); $log.scrollTop($log[0].scrollHeight); }; log("Loading SiteMatrix mapping..."); await loadSiteMatrix(); log("Mapping ready."); $('#gcusResultArea').show(); const sleep = ms => new Promise(r => setTimeout(r, ms)); async function robustFetch(url, wikiName, attempts = 0) { await sleep(600); // Fair-use delay const fullUrl = url + "&origin=*&contact=User:MrJaroslavik"; try { const response = await fetch(fullUrl); if (response.status === 429) { const retry = parseInt(response.headers.get('Retry-After')) || (30 * (attempts + 1)); log(`!! Rate limit on ${wikiName}. Waiting ${retry}s...`); await sleep(retry * 1000); return robustFetch(url, wikiName, attempts); } if (!response.ok) throw new Error(`HTTP ${response.status}`); return await response.json(); } catch (e) { if (attempts < 2) { log(`Retrying ${wikiName} (${attempts + 1}/3)...`); await sleep(2000); return robustFetch(url, wikiName, attempts + 1); } log(`!! Skipped project: ${wikiName}`); if (!$failed.val().includes(wikiName)) $failed.val($failed.val() + wikiName + ' (failed)\n'); return null; } } function checkChange(logEntry, roleName) { const p = logEntry.params || {}; const r = roleName.toLowerCase(); const normalize = (v) => Array.isArray(v) ? v.join(',').toLowerCase() : String(v || "").toLowerCase(); const oldStr = normalize(p.oldGroups || p.oldgroups || p.remove || p[0] || p["0"]); const newStr = normalize(p.newGroups || p.newgroups || p.add || p[1] || p["1"]); const hasRole = (s) => new RegExp('(^|,|\\s)' + r + '($|,|\\s)').test(s); if (!hasRole(oldStr) && hasRole(newStr)) return "added"; if (hasRole(oldStr) && !hasRole(newStr)) return "removed"; return null; } function getDaysDiff(dateFrom, dateTo) { if (dateTo === 'present') return 999; return Math.floor((new Date(dateTo) - new Date(dateFrom)) / 86400000); } const localArray = input.split('\n').map(s => s.trim()).filter(s => s.length > 0); const globalArray = [...new Set(localArray.map(s => { const parts = s.split('@'); parts.pop(); return parts.join('@'); }))]; let rawData = {}; log(`Scanning ${globalArray.length} unique accounts.`); // 1. GLOBAL ROLES for (let i = 0; i < globalArray.length; i++) { const name = globalArray[i]; $status.text(`Global: ${i+1}/${globalArray.length} (${name})`); let continueToken = ''; while (continueToken !== undefined) { const data = await robustFetch(`https://meta.wikimedia.org/w/api.php?action=query&list=logevents&letype=gblrights&letitle=User:${encodeURIComponent(name)}&lelimit=max${continueToken}&format=json`, 'metawiki'); if (!data) break; const logs = (data.query && data.query.logevents ? data.query.logevents : []).reverse(); logs.forEach(l => { const date = l.timestamp.substring(0, 10); ['steward', 'staff', 'ombuds'].forEach(role => { const change = checkChange(l, role); if (change === "added") { if (!rawData[name]) rawData[name] = []; rawData[name].push({ role, db: 'global', from: date, to: 'present' }); } else if (change === "removed") { const entry = (rawData[name] || []).filter(e => e.role === role && e.to === 'present').pop(); if (entry) entry.to = date; } }); }); continueToken = data.continue ? `&lecontinue=${data.continue.lecontinue}` : undefined; } } // 2. LOCAL ROLES for (let i = 0; i < localArray.length; i++) { const [name, db] = [localArray[i].split('@').slice(0,-1).join('@'), localArray[i].split('@').pop()]; // Skip disabled wikis if (disabledCUWikis.includes(db)) { if (!$failed.val().includes(db)) $failed.val($failed.val() + db + ' (CU disabled)\n'); continue; } if (db === 'loginwiki' && (rawData[name] || []).some(e => e.db === 'global' && e.role === 'steward')) continue; $status.text(`Local: ${i+1}/${localArray.length} (${name}@${db})`); const domain = siteMap[db] || `${db.replace('wiki', '').replace(/_/g, '-')}.wikipedia.org`; let continueToken = ''; try { while (continueToken !== undefined) { const data = await robustFetch(`https://${domain}/w/api.php?action=query&list=logevents&letype=rights&letitle=User:${encodeURIComponent(name)}&lelimit=max${continueToken}&format=json`, db); if (!data) break; const logs = (data.query && data.query.logevents ? data.query.logevents : []).reverse(); logs.forEach(l => { const date = l.timestamp.substring(0, 10); const change = checkChange(l, 'checkuser'); if (change === "added") { if (!rawData[name]) rawData[name] = []; rawData[name].push({ role: 'cu', db: db, from: date, to: 'present' }); } else if (change === "removed") { const entry = (rawData[name] || []).filter(e => e.role === 'cu' && e.db === db && e.to === 'present').pop(); if (entry) entry.to = date; } }); continueToken = data.continue ? `&lecontinue=${data.continue.lecontinue}` : undefined; } } catch(e) { log(`Error ${name}@${db}: ${e.message}`); } } // 3. FINAL LUA let lua = "return {\n users = {\n"; const sortedUsers = Object.keys(rawData).sort(); for (const user of sortedUsers) { const filtered = rawData[user].filter(e => e.db === 'global' || e.to === 'present' || getDaysDiff(e.from, e.to) >= 60); if (filtered.length > 0) { lua += ` ["${user}"] = {\n`; filtered.forEach(e => { lua += ` { role = "${e.role}", db = "${e.db}", from = "${e.from}", to = "${e.to}" },\n`; }); lua += ` },\n`; } } lua += " }\n}"; $('#gcusOutput').val(lua); $status.text("FINISHED!"); log("Scan complete."); $btn.prop('disabled', false).css('background', '#36c').text('RUN AGAIN'); } $(initInterface); })(); /* </nowiki> */ cpc22z2ln5tp1whs427oymz366pside 739785 739783 2026-04-29T17:17:55Z MrJaroslavik 44012 + 739785 javascript text/javascript /* <nowiki> */ /** * */ (function() { if (mw.config.get('wgCanonicalSpecialPageName') !== 'Blankpage' || mw.config.get('wgTitle').split('/').pop() !== 'GCUS-Data') { return; } const USERS_LIST = `.anaconda@kvwiki .anaconda@tiwiki .mau.@itwiki 0x010C@frwiki 1234qwer1234qwer4@loginwiki 1997kB@wikidatawiki 3(MG)²@frwiki A09@loginwiki A09@slwiki ABX@plwiki AJones (WMF)@enwiki AJones (WMF)@metawiki AKarani (WMF)@enwiki AKoval (WMF)@enwiki ATripathi-WMF@enwiki ATripathi-WMF@eswiki Aaron Schulz@enwiki Abisys@itwiki Acagastya@commonswiki Acagastya@enwikinews Adailton@ptwiki AdiJapan@rowiki Adler.fa@fawiki Adrian@skwiki Adrignola@enwikibooks Agony@fiwiki Ajraddatz@commonswiki Ajraddatz@enwiki Ajraddatz@eswiki Ajraddatz@loginwiki Ajraddatz@metawiki Akeron@frwiki Akoopal@nlwiki Alain r@frwiki Alan@commonswiki Alan@eswiki Alan@metawiki Albertoleoncio@loginwiki Albertoleoncio@ptwiki Aldnonymous@idwiki Alex Shih@enwiki Alexander Misel@zhwiki Alexanderps@ptwiki Alexanderps@ptwikinews Alhen@eswiki Alison@enwiki Alnokta@arwiki Alphax@commonswiki Alraunenstern@dewiki Amalthea@enwiki AmandaNP@enwiki AmandaNP@hrwiki AmandaNP@itwiki AmandaNP@loginwiki AmandaNP@wikidatawiki Andre Engels@acewiki Andre Engels@anwiki Andre Engels@barwiki Andre Engels@bgwiki Andre Engels@bswiki Andre Engels@dewiki Andre Engels@eswiktionary Andre Engels@fawiki Andre Engels@fawikiquote Andre Engels@kowiki Andre Engels@lvwiki Andre Engels@mswiki Andre Engels@nlwiki Andre Engels@ptwiki Andre Engels@ptwiktionary Andre Engels@sourceswiki Andre Engels@specieswiki Andre Engels@testwiki Andre Engels@urwiki Andre Engels@viwiki Andre Engels@zeawiki Andre Engels@zhwiki Andrei Stroe@rowiki Andrej Šalov@hrwiki Andriy.v@ukwiki Annabel@nlwiki Anr@fiwiki Anthere@enwiki Anthere@metawiki AntiCompositeNumber@loginwiki Antime@arwiki Aoidh@enwiki Aoineko@frwiki Aphaia@enwikiquote AramilFeraxa@loginwiki AramilFeraxa@metawiki AramilFeraxa@plwiki Aratal@frwiki Arcticocean@enwiki Arcticocean@hrwiki Arcticocean@kowiki Arcticocean@ptwiki Arcticocean@ukwiki Argo Navis@hrwiki Ash Crow@frwiki Asilvering@enwiki Ask21@itwiki Ausir@plwiki Avocato@arwiki Avraham@enwiki Avraham@loginwiki Azafred@enwiki B20180@thwiki BDD@enwiki BRPever@loginwiki BRPever@simplewiki BRPever@wikidatawiki Bahamut0013@enwiki Barak a@hewiki Barcex@eswiki Barkeep49@enwiki Barras@enwiki Barras@loginwiki Barras@metawiki Barras@simplewiki Base@loginwiki Bastique@azwiki Bastique@commonswiki Bastique@crwiki Bastique@enwiki Bastique@enwikibooks Bastique@enwikiquote Bastique@enwikiversity Bastique@eswiki Bastique@nowiki Bastique@plwiki Bastique@ruwiki Bastique@simplewiki Bastique@stwiki Bastique@svwiki Bastique@testwiki Bastique@trwiki Bastique@wikimania2008wiki Bastique@yiwiki Bastique@zhwiki Bbb23@enwiki Bdk@dewiki Beeblebrox@enwiki Bellcricket@jawiki Bencmq@zhwiki Benevolent Otter@plwiki Bennylin@idwiki Benoît Prieur@frwiki Berean Hunter@enwiki Beria@ptwiki Bernard@eswiki BetoCG@eswiki Billinghurst@enwikisource Billinghurst@eswikivoyage Billinghurst@loginwiki Billinghurst@metawiki Billinghurst@plwikinews Billinghurst@slwiki Bináris2@huwiki Bináris@huwiki Biologo32@ptwiki Blablubbs@enwiki BlackBeast@eswiki Borgx@idwiki Bradv@enwiki Brandon@enwiki BraneJ@srwiki Brian McNeil@enwikinews Brian@enwikinews Brooke Vibber@dewiki Brooke Vibber@enwiki Brooke Vibber@frwiki Brooke Vibber@mediawikiwiki Brooke Vibber@testwiki Bryan@commonswiki BryanDavis@labswiki Bsadowski1@loginwiki Bsadowski1@metawiki Bsadowski1@simplewiki CAshraf (WMF)@enwiki CLo (WMF)@testwiki CMaslak (WMF)@enwiki CMaslak (WMF)@fawiki CMaslak (WMF)@srwiki CMaslak (WMF)@uzwiki CPettet (WMF)@labswiki CSteigenberger (WMF)@cawiki CSteigenberger (WMF)@enwiki CSteigenberger (WMF)@jawiki CSteigenberger (WMF)@metawiki CSteipp (WMF)@ptwiktionary Cabayi@enwiki Caesar@svwiki Callanecc@enwiki CaptainEek@enwiki Carkuni@jawiki Cartman02au@metawiki Cary Bass@arwiki Cary Bass@aswiki Cary Bass@commonswiki Cary Bass@enwiki Cary Bass@enwikiquote Cary Bass@enwikiversity Cary Bass@frwiki Cary Bass@hiwiki Cary Bass@itwiki Cary Bass@metawiki Cary Bass@ptwiki Casliber@enwiki Cassiopeia sweet@jawiki Cato@enwikiquote Cdip150@zhwiki Cgt@dawiki Chaos@arwiki Chase me ladies, I'm the Cavalry@enwiki Chatama@jawiki Christian List@dawiki Christine (WMF)@enwiki Chuck Entz@enwiktionary Cinabrium@eswiki Ciphers@arwiki Cirdan@dewiki Cirt@enwikinews Civvi~itwiki@itwiki Clem23@frwiki Codc@dewiki CodeMonk@ruwiki Coet@cawiki Conde Edmond Dantès@ptwiki Connel MacKenzie@enwiktionary Cool Hand Luke@enwiki Coren@enwiki Coren@labswiki Count Count@dewiki Count Count@loginwiki Courcelles@enwiki Creol@simplewiki Cromium@elwiki Cromium@enwikinews Cromium@loginwiki Cromium@rowiki Cromium@sqwiki Cromium@zhwiki Cruccone@itwiki Csigabi@huwiki Cspurrier@enwikinews Cspurrier@enwikiquote Cspurrier@enwikiversity Cspurrier@fawiki Cspurrier@itwiki Cspurrier@metawiki Cspurrier@ptwiki Cspurrier@simplewiki Cspurrier@ukwiki Cspurrier@zhwiki Céréales Killer@frwiki DBarratt (WMF)@testwiki DGG@enwiki DHN@viwiki DJackson (WMF)@testwiki DMaza (WMF)@testwiki DR@ruwiki DWalden (WMF)@frwiki DWalden (WMF)@testwiki Daedalus@svwiki Daimore@ptwiki Damzow@hewiki Dan Koehl@specieswiki Daniel Quinlan@enwiki Daniel@enwiki Daniuu@arwiki Daniuu@loginwiki Daniuu@metawiki Daniuu@nlwiki Daniuu@testwiki Danmichaelo@nowiki DannyH (WMF)@mediawikiwiki DannyH (WMF)@testwiki DannyS712@testwiki Danu Widjajanto@idwiki Darkoneko@aswiki Darkoneko@brwiki Darkoneko@bugwiki Darkoneko@cswiki Darkoneko@cswikisource Darkoneko@cswiktionary Darkoneko@dawiki Darkoneko@enwiki Darkoneko@enwikiversity Darkoneko@eswiki Darkoneko@fawiki Darkoneko@frwiki Darkoneko@frwikiquote Darkoneko@glwiki Darkoneko@huwiki Darkoneko@jawiktionary Darkoneko@mediawikiwiki Darkoneko@metawiki Darkoneko@mlwiki Darkoneko@newiki Darkoneko@nlwikiquote Darkoneko@ruwiktionary Darkoneko@skwiki Darkoneko@skwikisource Darkoneko@sourceswiki Darkoneko@srwiki Darkoneko@stwiki Darkoneko@testwiki Darkoneko@trwiki Darkoneko@ukwiki Darkoneko@viwiki Darkoneko@warwiki Darkoneko@wawiki Darkoneko@wowiki Darkoneko@yiwiki DatGuy@enwiki Datrio@fawiki David Fuchs@enwiki David Gerard@enwiki David Wadie Fisher-Freberg@idwiki Dbeef@enwiki Dbl2010@azwiki Dbl2010@kowiktionary Dbl2010@testwiki Dbl2010@trwiki Dbl2010@vlswiki Dcastor@svwiki Deineka@ukwiki Demicx@bswiki Der-Wir-Ing@dewiki DerHexer@loginwiki DerHexer@metawiki Derbeth@enwikibooks Deror avi@hewiki Deskana (WMF)@enwiki Deskana (WMF)@mediawikiwiki Deskana@enwiki Dferg@acewiki Dferg@bat_smgwiki Dferg@bewiki Dferg@bgwiki Dferg@brwiki Dferg@cebwiki Dferg@enwikiversity Dferg@eowiki Dferg@extwiki Dferg@fawiki Dferg@frwikiversity Dferg@gdwiki Dferg@glwiktionary Dferg@hawwiki Dferg@hiwiki Dferg@hywiki Dferg@iswiki Dferg@itwikiquote Dferg@jawikiquote Dferg@kawiki Dferg@kowiki Dferg@kshwiki Dferg@lawiki Dferg@lvwiki Dferg@mkwiktionary Dferg@mswiki Dferg@ndswiki Dferg@orwiktionary Dferg@pmswiki Dferg@ptwikibooks Dferg@ptwikisource Dferg@ptwiktionary Dferg@quwiki Dferg@rowiki Dferg@siwiki Dferg@specieswiki Dferg@sqwiki Dferg@swwiki Dferg@tewiki Dferg@ukwiki Dferg@uzwiki Dferg@viwiki Dferg@vowiki Dferg@wuuwiki Dferg@xalwiki Dferg@xhwiki Dferg@zh_classicalwiki Dferg@zh_yuewiki Dferg@zhwikinews Djordjes@srwiki Djsasso@simplewiki Dmcdevit@wikimania2008wiki Dmitry Gerasimov@ruwiki Dmthoth@kowiki DoRD@enwiki Dominic@enwiki Dominic@enwiktionary Doug Weller@enwiki Doğu@commonswiki Doğu@itwiki Dr-Taher@arwiki Dragoniez@jawiki Drahreg01@dewiki Drbug@ruwiki Dreamy Jazz@enwiki Dreamy Jazz@testwiki Drini@aawiki Drini@afwiki Drini@anwiki Drini@astwiki Drini@azwiki Drini@bgwiki Drini@brwiki Drini@cywiki Drini@dawikibooks Drini@dsbwiki Drini@enwikibooks Drini@enwikiquote Drini@enwikisource Drini@enwikiversity Drini@eowiki Drini@eswiki Drini@etwiki Drini@euwiktionary Drini@extwiki Drini@fawiki Drini@fiwiki Drini@fiwikibooks Drini@fiwikinews Drini@fiwikisource Drini@frpwiki Drini@frwikiquote Drini@gawiki Drini@glwiki Drini@hakwiki Drini@hiwiki Drini@idwiki Drini@iewiki Drini@igwiki Drini@incubatorwiki Drini@iswiki Drini@iswikibooks Drini@itwikisource Drini@itwikisource Drini@jvwiki Drini@kmwiki Drini@kowikiquote Drini@lawiki Drini@lbewiki Drini@lgwiki Drini@lmowiki Drini@lowiki Drini@lvwiki Drini@mediawikiwiki Drini@mlwiki Drini@mswiki Drini@ngwiki Drini@nlwikiquote Drini@nowiki Drini@nowikisource Drini@nvwiki Drini@piwiki Drini@rnwiki Drini@rowiki Drini@ruwiki Drini@ruwikinews Drini@ruwikiquote Drini@scnwiktionary Drini@sgwiki Drini@shwiki Drini@skwiki Drini@snwiki Drini@sowiki Drini@sqwiki Drini@srwiki Drini@swwiki Drini@testwiki Drini@tlwiki Drini@towiki Drini@ukwiki Drini@ukwikinews Drini@ukwiktionary Drini@viwiki Drini@wuuwiki Drini@xalwiki Drini@xhwiki Drini@yiwiki Drini@zh_yuewiki Drini@zhwiki Drmies@enwiki Dtom@hrwiki Dungodung@metawiki Dungodung@srwiki Dungodung@testwiki Durifon@frwiki Dyolf77@arwiki Dyolf77@frwiki Dzordzm@srwiki E.coli@hrwiki EMill-WMF@enwiki EPIC@enwiki EPIC@loginwiki EPIC@metawiki EVula@enwikiquote EdJohnston@enwiki Edmenb@eswiki Effeietsanders@dewiktionary Effeietsanders@enwikiquote Effeietsanders@enwikiversity Effeietsanders@fawiki Effeietsanders@kowiki Effeietsanders@kshwiki Effeietsanders@kuwiki Effeietsanders@nowiki Effeietsanders@ptwiki Effeietsanders@testwiki Einsbor@enwiki Einsbor@loginwiki Ejs-80@fiwiki Elcobbola@commonswiki Eldarion@trwiki Elen of the Roads@enwiki Elfix@cywiktionary Elfix@dewikibooks Elfix@frwiki Elfix@idwikiquote Elfix@itwikinews Elfix@loginwiki Elfix@mywiki Elfix@ptwikibooks Elfix@ptwikiquote Elfix@ptwikisource Elian@dewiki Elli@enwiki Ellywa@nlwiki Elmacenderesi@trwiki Elockid@enwiki Elph@arwiki Elton@astwiki Elton@cebwiki Elton@chwiki Elton@incubatorwiki Elton@jawikinews Elton@loginwiki Elton@mediawikiwiki Elton@plwikiquote Elton@ptwikiquote Elton@shwiki Elton@testwiki Elton@wikidatawiki Elton@zhwiktionary Elwood@itwiki Emir Kotromanić@bswiki Emufarmers@enwiki Emufarmers@eswiki Emufarmers@metawiki Emx@bswiki Enterprisey@enwiki Epinheiro@ptwiki Eptalon@simplewiki Erkan Yilmaz@enwikiversity Erwin85@zhwiki Erwin@nlwiki Essjay@enwiki Eta Carinae@ptwiki Euryalus@enwiki Eusebius@commonswiki EvgenyGenkin@ruwiki Ex13@hrwiki FT2@enwiki Faendalimas@specieswiki Faso@jawiki FayssalF@enwiki Felipe da Fonseca@ptwiki Ferret@enwiki Filzstift@dewiki Firefly@enwiki Fitindia@commonswiki FloNight@enwiki Fluff@svwiki FoBe@huwiki Fr33kman@simplewiki Frank@enwiki Fred Bauder@enwiki Freetrashbox@jawiki Fritzpoll@enwiki Gac@itwiki Galahad@eswiki Galahad@fawiki Galahad@trwiki Gamaliel@enwiki GeneralNotability@enwiki Geonuch@thwiki Giraffer@enwiki Girth Summit@enwiki Gmaxwell@commonswiki Gnom@dewiki Goo3@ukwiki GorillaWarfare@enwiki Gpvos@nlwiki Gribeco@frwiki Grin@huwiki Groucho NL@nlwiki Guerillero@enwiki Guillom@arwiki Guillom@cvwiki Guillom@dawiki Guillom@fawiki Guillom@fiwikiquote Guillom@foundationwiki Guillom@frwiki Guillom@frwikinews Guillom@frwikisource Guillom@frwiktionary Guillom@iawiki Guillom@ilowiki Guillom@iowiki Guillom@iswiki Guillom@itwikinews Guillom@kabwiki Guillom@kowiki Guillom@rowiki Guillom@siwiktionary Guillom@trwiki Gutza@rowiki Gvf@itwiki Góngora@cawiki HJ Mitchell@enwiki HVL@ptwiki Ha98574@kowiki HaeB@dewiki HaithamS (WMF)@enwiki HakanIST@loginwiki Hardscarf@nlwiki Harel@hewiki Haros@nowiki Harriv@fiwiki Hasley@barwiki Hasley@loginwiki Hasley@metawiki Hayabusa future@idwiki Hei ber@dewiki Hei ber@enwiki Hephaion@dewiki Herbythyme@commonswiki Herbythyme@enwikibooks Herbythyme@metawiki Herbythyme@nowiki Hersfold@enwiki Hexasoft@frwiki Hidayatsrf@idwiki Hidro@hewiki Hispa@eswiki Hjvannes@nlwiki Hoo man@loginwiki HouseBlaster@enwiki Huji@fawiki Hunyadym@huwiki Hyméros@frwiki INeverCry@commonswiki IRTC1015@kowiki Ilya Voyager@ruwiki IlyaHaykinson@enwikinews Indech@ptwiki Infinite0694@eswiki Infinite0694@jawiki Infinite0694@loginwiki Infinite0694@metawiki Iridescent@enwiki IvanLanin@idwiki Ivanvector@enwiki Izno@enwiki J.delanoy@enwiki JAbrams (WMF)@cebwiki JAbrams (WMF)@commonswiki JAbrams (WMF)@cswiktionary JAbrams (WMF)@enwiki JAbrams (WMF)@eowiki JAbrams (WMF)@frwiki JAbrams (WMF)@jawiki JAbrams (WMF)@metawiki JAbrams (WMF)@ptwiki JAbrams (WMF)@wikidatawiki JCrespo (WMF)@labswiki JEissfeldt (WMF)@dewiki JEissfeldt (WMF)@enwiki JEissfeldt (WMF)@jawiki JJMC89@eswiki JJMC89@loginwiki JSutherland (WMF)@afwiki JSutherland (WMF)@arwiki JSutherland (WMF)@arwikisource JSutherland (WMF)@azwiki JSutherland (WMF)@bgwiki JSutherland (WMF)@commonswiki JSutherland (WMF)@cswiki JSutherland (WMF)@dawiki JSutherland (WMF)@dewiki JSutherland (WMF)@enwiki JSutherland (WMF)@enwikibooks JSutherland (WMF)@enwikiquote JSutherland (WMF)@enwiktionary JSutherland (WMF)@eswiki JSutherland (WMF)@fawiki JSutherland (WMF)@frwiki JSutherland (WMF)@frwikiquote JSutherland (WMF)@frwikiversity JSutherland (WMF)@hewiki JSutherland (WMF)@hrwiki JSutherland (WMF)@huwiki JSutherland (WMF)@hywiki JSutherland (WMF)@idwiki JSutherland (WMF)@itwiki JSutherland (WMF)@jawiki JSutherland (WMF)@kowiki JSutherland (WMF)@loginwiki JSutherland (WMF)@mediawikiwiki JSutherland (WMF)@metawiki JSutherland (WMF)@nlwiki JSutherland (WMF)@nowiki JSutherland (WMF)@plwiki JSutherland (WMF)@ptwiki JSutherland (WMF)@ruwiki JSutherland (WMF)@simplewiki JSutherland (WMF)@svwiki JSutherland (WMF)@testwiki JSutherland (WMF)@ukwiki JSutherland (WMF)@wikidatawiki JSutherland (WMF)@zhwiki JWSchmidt@enwikiversity JWang (WMF)@testwiki Jafeluv@fiwiki Jagro@cswiki Jake Park@eswiki Jalexander-WMF@arwiki Jalexander-WMF@azwiki Jalexander-WMF@bgwiki Jalexander-WMF@cawiki Jalexander-WMF@commonswiki Jalexander-WMF@cswiki Jalexander-WMF@dawiki Jalexander-WMF@dewiki Jalexander-WMF@enwiki Jalexander-WMF@enwikibooks Jalexander-WMF@enwikiquote Jalexander-WMF@enwikisource Jalexander-WMF@enwikiversity Jalexander-WMF@enwikivoyage Jalexander-WMF@enwiktionary Jalexander-WMF@eswiki Jalexander-WMF@eswikivoyage Jalexander-WMF@frwiki Jalexander-WMF@gawiki Jalexander-WMF@hewiki Jalexander-WMF@hewikisource Jalexander-WMF@incubatorwiki Jalexander-WMF@iswiki Jalexander-WMF@itwiki Jalexander-WMF@kowiki Jalexander-WMF@kowikibooks Jalexander-WMF@kowikisource Jalexander-WMF@loginwiki Jalexander-WMF@mediawikiwiki Jalexander-WMF@metawiki Jalexander-WMF@mywiki Jalexander-WMF@nowiki Jalexander-WMF@nowiktionary Jalexander-WMF@plwiki Jalexander-WMF@ptwiki Jalexander-WMF@ruwiki Jalexander-WMF@ruwikibooks Jalexander-WMF@ruwikisource Jalexander-WMF@simplewiki Jalexander-WMF@specieswiki Jalexander-WMF@svwiki Jalexander-WMF@testwiki Jalexander-WMF@wikidatawiki Jalexander-WMF@zhwiki Jalexander@foundationwiki Jalexander@labswiki James LL@loginwiki Jameslwoodward@commonswiki Jamesofur@simplewiki Jamie Tubers@enwiki Janorovic Volkov@idwiki Japiot@nlwiki Jasper Deng@wikidatawiki Jayjg@enwiki Jbribeiro1@ptwiki Jcb@nlwiki Jclemens@enwiki Jdforrester (WMF)@enwiki Jdforrester (WMF)@enwikiquote Jdforrester@enwiki Jean-Christophe BENOIST@frwiki Jeffq@enwikiquote Jimbo Wales@enwiki Jimmy Xu@zhwiki Jmrebes@cawiki Jniemenmaa@fiwiki Joe Roe@enwiki Johannnes89@loginwiki Johannnes89@metawiki John Vandenberg@enwiki John Vandenberg@enwikisource Jon Harald Søby@akwiki Jon Harald Søby@alswiki Jon Harald Søby@cawiki Jon Harald Søby@cswiki Jon Harald Søby@dewiki Jon Harald Søby@enwiki Jon Harald Søby@enwikiquote Jon Harald Søby@fawiki Jon Harald Søby@fiwikibooks Jon Harald Søby@fiwikisource Jon Harald Søby@frwiki Jon Harald Søby@iswiki Jon Harald Søby@itwiki Jon Harald Søby@kowiki Jon Harald Søby@metawiki Jon Harald Søby@nnwiki Jon Harald Søby@nowiki Jon Harald Søby@nowikibooks Jon Harald Søby@plwikibooks Jon Harald Søby@plwikinews Jon Harald Søby@plwiktionary Jon Harald Søby@ruwiki Jon Harald Søby@skwiki Jon Harald Søby@svwiki Jon Harald Søby@svwikinews Jon Harald Søby@testwiki Jon Harald Søby@trwiki Jon Harald Søby@yiwiki Jon Kolbert@loginwiki Joutbis@cawiki Jpgordon@enwiki Jrogers (WMF)@metawiki Julle@svwiki Julle@testwiki Just Sayori@thwiki JusteJuju10@frwiki Jyothis@eswiktionary Jyothis@loginwiki Jyothis@metawiki KColeman-WMF@testwiki KHarlan (WMF)@loginwiki KHarlan (WMF)@testwiki KLevan (WMF)@arwiki KLevan (WMF)@commonswiki KLevan (WMF)@enwiki KLevan (WMF)@hewiki KLevan (WMF)@idwiki KLevan (WMF)@metawiki KMT@jawiki KRLS@cawiki Kaare@dawiki Kal-El~bswiki@bswiki Kalliope (WMF)@azwiki Kalliope (WMF)@commonswiki Kalliope (WMF)@dewiki Kalliope (WMF)@enwiki Kalliope (WMF)@enwikiversity Kalliope (WMF)@enwiktionary Kalliope (WMF)@eswiki Kalliope (WMF)@frwiki Kalliope (WMF)@frwiktionary Kalliope (WMF)@idwiki Kalliope (WMF)@itwiki Kalliope (WMF)@jawiki Kalliope (WMF)@loginwiki Kalliope (WMF)@metawiki Kalliope (WMF)@outreachwiki Kalliope (WMF)@testwiki Kalliope (WMF)@zhwiki Kanjy@jawiki Karol007@plwiki Karsten11@dewiki Kbrown (WMF)@arwiki Kbrown (WMF)@commonswiki Kbrown (WMF)@dewiki Kbrown (WMF)@enwiki Kbrown (WMF)@enwikibooks Kbrown (WMF)@enwikiversity Kbrown (WMF)@enwikivoyage Kbrown (WMF)@eowiki Kbrown (WMF)@eswiki Kbrown (WMF)@eswikibooks Kbrown (WMF)@frwiki Kbrown (WMF)@frwiktionary Kbrown (WMF)@itwiki Kbrown (WMF)@loginwiki Kbrown (WMF)@metawiki Kbrown (WMF)@ptwiki Kbrown (WMF)@simplewiki Kbrown (WMF)@testwiki Kbrown (WMF)@trwiki Kbrown (WMF)@zhwiki Keegan@enwiki Kegns@zhwiki Kelapstick@enwiki Kiran Gopi@mlwiki Kirill Lokshin@enwiki KnightLago@enwiki KnudW@dawiki Koavf@specieswiki KonstantinaG07@loginwiki KovacsUr@huwiki KrakatoaKatie@enwiki Krd@commonswiki Krd@loginwiki Krd@metawiki Ks aka 98@jawiki Ks0stm@enwiki Kulac@dewiki Kv75@ruwiki Kylu@enwiki Kylu@metawiki L235@enwiki LD@frwiki LFaraone@enwiki Laaknor@metawiki Laaknor@nowiki Ladsgroup@fawiki Ladsgroup@labswiki Ladsgroup@testwiki Ladypine@hewiki Lankiveil@enwiki Lanwi1@zhwiki Lar@alswiki Lar@anwiki Lar@arwiki Lar@bgwiki Lar@commonswiki Lar@dewiki Lar@enwiki Lar@enwikiquote Lar@enwikisource Lar@enwikiversity Lar@fawiki Lar@frwiki Lar@hywiki Lar@kywiktionary Lar@metawiki Lar@mswiki Lar@nlwiki Lar@nlwikiquote Lar@plwiki Lar@rowiki Lar@scowiki Lar@simplewiki Lar@simplewiktionary Lar@thwiki Lar@tiwiki Lar@trwiki Lar@ukwiki Lar@viwiki Lar@zhwiki Le chat perché@frwiki Lechatjaune@ptwiki Leinad pl@rowiki Leinad@metawiki Leinad@plwiki Lewisiscrazy@frwiki Lijealso@ptwiki Linedwell@frwiki Linedwell@loginwiki Liso@skwiki Little Sunshine@ptwiki Lofty abyss@abwiki Lofty abyss@acewiki Lofty abyss@astwiki Lofty abyss@bclwiki Lofty abyss@betawikiversity Lofty abyss@bnwiki Lofty abyss@cawiktionary Lofty abyss@chrwiki Lofty abyss@crwiki Lofty abyss@cswikibooks Lofty abyss@cswiktionary Lofty abyss@dawikisource Lofty abyss@dawiktionary Lofty abyss@dewikibooks Lofty abyss@dewikinews Lofty abyss@dewiktionary Lofty abyss@eewiki Lofty abyss@enwikibooks Lofty abyss@enwikiversity Lofty abyss@enwikivoyage Lofty abyss@etwiki Lofty abyss@fawiki Lofty abyss@glwiki Lofty abyss@huwiktionary Lofty abyss@idwikibooks Lofty abyss@idwikiquote Lofty abyss@idwikisource Lofty abyss@idwiktionary Lofty abyss@iewiki Lofty abyss@ilowiki Lofty abyss@incubatorwiki Lofty abyss@itwikisource Lofty abyss@itwikisource Lofty abyss@jvwiki Lofty abyss@kmwiki Lofty abyss@kowikiquote Lofty abyss@lawiki Lofty abyss@lbewiki Lofty abyss@lgwiki Lofty abyss@lmowiki Lofty abyss@lowiki Lofty abyss@lvwiki Lofty abyss@mediawikiwiki Lofty abyss@metawiki Lofty abyss@mnwiki Lofty abyss@mswiki Lofty abyss@ndswiki Lofty abyss@nlwikibooks Lofty abyss@nlwikiquote Lofty abyss@nlwiktionary Lofty abyss@nowiki Lofty abyss@nycwikimedia Lofty abyss@nywiki Lofty abyss@omwiki Lofty abyss@outreachwiki Lofty abyss@papwiki Lofty abyss@pflwiki Lofty abyss@pihwiki Lofty abyss@plwikiquote Lofty abyss@plwikisource Lofty abyss@ptwikibooks Lofty abyss@ptwikinews Lofty abyss@ptwikiquote Lofty abyss@ptwikiversity Lofty abyss@ptwikivoyage Lofty abyss@ptwiktionary Lofty abyss@quwiki Lofty abyss@simplewiki Lofty abyss@simplewiktionary Lofty abyss@sourceswiki Lofty abyss@specieswiki Lofty abyss@suwiki Lofty abyss@svwikinews Lofty abyss@svwikiquote Lofty abyss@swwiki Lofty abyss@tiwiktionary Lofty abyss@trwikisource Lofty abyss@vewiki Lofty abyss@warwiki Lofty abyss@wikidatawiki Lofty abyss@wuuwiki Lofty abyss@yiwiki Lofty abyss@yowiki Lofty abyss@zh_classicalwiki Lord Mota@ptwiki Luk@enwiki Luna Santin@enwiki Lusitana@ptwiki Lusum@itwiki Lymantria@commonswiki Lymantria@wikidatawiki Lzur@plwiki M7@kawiki M7@loginwiki M7@metawiki M7@simplewiki MAna (WMF)@testwiki MBisanz@enwiki MBq@dewiki MF-Warburg@loginwiki MFischer (WMF)@enwiki MGA73@dawiki MGodwin@enwiki MPelletier (WMF)@enwiki MPelletier (WMF)@wikidatawiki MPinchuk (WMF) (usurped)@mediawikiwiki MPostoronca-WMF@testwiki MSzabo-WMF@testwiki MZaplotnik@slwiki Mackensen@enwiki Madaki@itwiki Magister Mathematicae@akwiki Magister Mathematicae@alswiki Magister Mathematicae@angwiki Magister Mathematicae@arwiki Magister Mathematicae@aswiki Magister Mathematicae@azwiktionary Magister Mathematicae@barwiki Magister Mathematicae@bawiki Magister Mathematicae@bpywiki Magister Mathematicae@bugwiki Magister Mathematicae@cawiki Magister Mathematicae@chwiki Magister Mathematicae@commonswiki Magister Mathematicae@cowiki Magister Mathematicae@cswiki Magister Mathematicae@enwiki Magister Mathematicae@enwikinews Magister Mathematicae@eswiki Magister Mathematicae@eswikibooks Magister Mathematicae@eswikinews Magister Mathematicae@eswikiquote Magister Mathematicae@eswikisource Magister Mathematicae@eswikiversity Magister Mathematicae@fawiki Magister Mathematicae@ffwiki Magister Mathematicae@fiwikiquote Magister Mathematicae@fjwiki Magister Mathematicae@gawiktionary Magister Mathematicae@gdwiki Magister Mathematicae@itwikibooks Magister Mathematicae@kabwiki Magister Mathematicae@kowiki Magister Mathematicae@kwwiktionary Magister Mathematicae@metawiki Magister Mathematicae@nahwiki Magister Mathematicae@pihwiki Magister Mathematicae@ptwiki Magister Mathematicae@quwiki Magister Mathematicae@simplewiki Magister Mathematicae@simplewiktionary Magister Mathematicae@specieswiki Magister Mathematicae@stwiki Magister Mathematicae@tswiki Magister Mathematicae@ugwiki Magister Mathematicae@uzwiki Magister Mathematicae@zawiki MagnusA@svwiki Magog the Ogre@commonswiki Mailer diablo@enwiki Majorly@simplewiki Malatinszky@huwiki Marc Mongenet@frwiki MarcGarver@astwiki MarcGarver@azwikibooks MarcGarver@bxrwiki MarcGarver@cewiki MarcGarver@chrwiktionary MarcGarver@crwiki MarcGarver@cswikibooks MarcGarver@cswikiquote MarcGarver@enwikibooks MarcGarver@enwikiquote MarcGarver@enwikiversity MarcGarver@enwikivoyage MarcGarver@eswikiquote MarcGarver@fawiki MarcGarver@hawiki MarcGarver@hewikivoyage MarcGarver@hiwiki MarcGarver@idwikiquote MarcGarver@idwikisource MarcGarver@incubatorwiki MarcGarver@kabwiki MarcGarver@loginwiki MarcGarver@ltwiktionary MarcGarver@mediawikiwiki MarcGarver@mswiki MarcGarver@nlwikimedia MarcGarver@nlwikivoyage MarcGarver@nowiki MarcGarver@piwiki MarcGarver@ptwikivoyage MarcGarver@scowiki MarcGarver@slwiki MarcGarver@svwikivoyage MarcGarver@wuuwiki MarcGarver@zhwiki Marcelo Victor@ptwiki MarcoAurelio@astwiki MarcoAurelio@barwiki MarcoAurelio@chrwiki MarcoAurelio@eewiki MarcoAurelio@enwiki MarcoAurelio@eswikiquote MarcoAurelio@euwiki MarcoAurelio@glwiki MarcoAurelio@loginwiki MarcoAurelio@mediawikiwiki MarcoAurelio@metawiki MarcoAurelio@nowiki MarcoAurelio@ptwiki MarcoAurelio@ptwikiquote MarcoAurelio@sourceswiki MarcoAurelio@testwiki MarcoAurelio@zhwiki Mardetanha@commonswiki Mardetanha@enwiki Mardetanha@loginwiki Mardetanha@metawiki Marine-Blue@jawiki Mark Bergsma@enwiki Mark Bergsma@nlwiki Markov@frwiki Martin H.@commonswiki Martin Urbanec (WMF)@cswiki Martin Urbanec (WMF)@cswikiversity Martin Urbanec (WMF)@loginwiki Martin Urbanec (WMF)@metawiki Martin Urbanec (WMF)@testwiki Martin Urbanec@cswiki Martin Urbanec@enwiki Martin Urbanec@loginwiki Martin Urbanec@metawiki Martin Urbanec@testwiki Masti@loginwiki Masti@plwiki Matanya@enwiki Matanya@hewiki Matanya@testwiki Materialscientist@enwiki Mathis B@frwiki Mathonius@loginwiki Matiia@loginwiki MatthiasGor@plwiki Maurilbert@frwiki MaxSem@bgwiki MaxSem@elwiki MaxSem@enwiki MaxSem@enwikiquote MaxSem@enwikisource MaxSem@fowiktionary MaxSem@hrwiki MaxSem@metawiki MaxSem@nowiki MaxSem@nowikibooks MaxSem@plwiki MaxSem@shwiktionary MaxSem@simplewiki MaxSem@skwiki MaxSem@srwiki MaxSem@stwiki MaxSem@svwiki MaxSem@ukwiki MaxSem@yiwiki MaxSem@zhwiki Maxim@enwiki Mdennis (WMF)@commonswiki Mdennis (WMF)@dewiki Mdennis (WMF)@enwiki Mdennis (WMF)@enwikinews Mdennis (WMF)@enwikiquote Mdennis (WMF)@enwikisource Mdennis (WMF)@frwiki Mdennis (WMF)@frwiktionary Mdennis (WMF)@loginwiki Mdennis (WMF)@metawiki Mdennis (WMF)@simplewiki Mdennis (WMF)@svwiki MdsShakil@loginwiki MdsShakil@test2wiki MdsShakil@testwiki Melos@itwiki Melos@loginwiki Melos@sswiki Meno25@arwiki Mentisock@wikidatawiki Mercy@metawiki Meursault2004@idwiki Mido@arwiki Midom@enwiki Midom@metawiki Mike V@enwiki Mike.lifeguard@commonswiki Mike.lifeguard@enwikibooks Mike.lifeguard@metawiki Mike.lifeguard@pmswiki Mike.lifeguard@rmywiki Mike.lifeguard@tywiki MikkoM@fiwiki Millennium bug@ptwiki Millosh@azwiki Millosh@bswiki Millosh@cswiki Millosh@dewiktionary Millosh@enwiki Millosh@fawiki Millosh@gdwiktionary Millosh@hrwiki Millosh@kowiki Millosh@metawiki Millosh@pmswiki Millosh@ruwiki Millosh@ruwikisource Millosh@sqwiki Millosh@trwiki Millosh@zhwiki Minderbinder@dewiki Minorax@metawiki Mkdw@enwiki Mmenal@frwiki Moneytrees@enwiki Montgomery@eswiki Mormegil@cswiki Morven@enwiki MrJaroslavik@loginwiki MrJaroslavik@testwiki Msz2001@plwiki Mtarch11@itwiki Mtarch11@metawiki MuZemike@enwiki MusikAnimal@enwiki MusikAnimal@loginwiki Mwpnl@trwiki Mxn@viwiki Mykola7@loginwiki Mykola7@ukwiki Mys 721tx@zhwiki Mz7@enwiki NForrester (WMF)@bewiki NForrester (WMF)@bgwiki NForrester (WMF)@commonswiki NForrester (WMF)@enwiki NForrester (WMF)@eswiki NForrester (WMF)@metawiki NForrester (WMF)@ruwiki NForrester (WMF)@shwiki NForrester (WMF)@srwiki NForrester (WMF)@wikidatawiki NForrester (WMF)@zhwiki NKohli (WMF)@testwiki NNair (WMF)@enwiki NahidSultan (WMF)@arwiki NahidSultan (WMF)@arwikinews NahidSultan (WMF)@bnwiki NahidSultan (WMF)@bnwiktionary NahidSultan (WMF)@cawiki NahidSultan (WMF)@commonswiki NahidSultan (WMF)@enwiki NahidSultan (WMF)@enwikinews NahidSultan (WMF)@enwikiquote NahidSultan (WMF)@eswiki NahidSultan (WMF)@ffwiki NahidSultan (WMF)@foundationwiki NahidSultan (WMF)@frwiki NahidSultan (WMF)@hewiki NahidSultan (WMF)@jawiki NahidSultan (WMF)@jawiktionary NahidSultan (WMF)@knwiki NahidSultan (WMF)@kowiki NahidSultan (WMF)@loginwiki NahidSultan (WMF)@mediawikiwiki NahidSultan (WMF)@metawiki NahidSultan (WMF)@miwiki NahidSultan (WMF)@mkwiki NahidSultan (WMF)@mrwiki NahidSultan (WMF)@mswiki NahidSultan (WMF)@ptwiki NahidSultan (WMF)@rowiki NahidSultan (WMF)@ruwiki NahidSultan (WMF)@simplewiki NahidSultan (WMF)@specieswiki NahidSultan (WMF)@srwiki NahidSultan (WMF)@tawiki NahidSultan (WMF)@ukwiki NahidSultan (WMF)@uzwiki NahidSultan (WMF)@wikidatawiki NahidSultan (WMF)@zhwiki NahidSultan@bnwiki NahidSultan@loginwiki Nakor@frwiki Nataev@uzwiki NativeForeigner@enwiki Natuur12@nlwiki Nedops@plwiki Nelson Teixeira@ptwiki Nettadi@hewiki Neukoln@hewiki Newyorkbrad@enwiki Nick1915@bat_smgwiki Nick1915@elwiki Nick1915@fawiki Nick1915@itwikibooks Nick1915@itwiktionary Nick1915@kowiki Nick1915@kwwiki Nick1915@lmowiki Nick1915@metawiki Nick1915@ptwiki Nick1915@scnwiki Nick1915@trwiki Nick1915@zhwiki NickK@ukwiki NinjaRobotPirate@enwiki Nishkid64@enwiki NoFWDaddress@frwiki Nohirara@idwiki Noumenon@trwiki NuclearWarfare@enwiki Nuno Tavares@ptwiki Nux@plwiki Nyenyec@huwiki OJJ@cswiki Octahedron80@thwiki OhanaUnited@enwikivoyage OneLittleMouse@ruwiki OnlyJonny@ptwiki Opabinia regalis@enwiki Operator873@loginwiki Operator873@simplewiki Ori@hewiki Oscar@commonswiki Oscar@dewiki Oscar@enwiki Oscar@frwiki Oscar@metawiki Oscar@nlwiki Oscar@nlwikibooks Oscarami@eswiki Oshwah@enwiki PEarley (WMF)@enwiki PSaxena (WMF)@mediawikiwiki PSaxena (WMF)@testwiki Paginazero@commonswiki Paginazero@enwiki Paginazero@enwikiquote Paginazero@itwiki Paginazero@itwikibooks Paginazero@ruwiki Paginazero@yiwiki Pallerti@huwiki Palnatoke@dawiki Pap3rinik@itwiki Pathoschild@commonswiki Pathoschild@cywiki Pathoschild@elwiki Pathoschild@enwiki Pathoschild@enwikinews Pathoschild@enwikiquote Pathoschild@enwikisource Pathoschild@eowiki Pathoschild@eswiki Pathoschild@eswikiquote Pathoschild@etwiki Pathoschild@fawiki Pathoschild@fiwikinews Pathoschild@hewiki Pathoschild@hsbwiki Pathoschild@huwiki Pathoschild@idwiki Pathoschild@incubatorwiki Pathoschild@itwiki Pathoschild@itwikiquote Pathoschild@klwiki Pathoschild@kwwiktionary Pathoschild@lbwiki Pathoschild@metawiki Pathoschild@miwiki Pathoschild@mlwiki Pathoschild@nlwiki Pathoschild@nnwiki Pathoschild@ocwiki Pathoschild@ptwiki Pathoschild@ruwiktionary Pathoschild@simplewiki Pathoschild@siwiki Pathoschild@sqwiki Pathoschild@srwiki Pathoschild@urwiktionary Pathoschild@vewiki Pathoschild@yiwiki Pathoschild@zhwiki Pathoschild@zhwiktionary PatríciaR@ptwiki Penn Station@jawiki Perrak@dewiki Pete Forsyth (WMF)@enwiki PeterSymonds@metawiki Peterdownunder@simplewiki PhilKnight@enwiki Philippe (WMF)@azwiki Philippe (WMF)@commonswiki Philippe (WMF)@dewiki Philippe (WMF)@enwiki Philippe (WMF)@enwikibooks Philippe (WMF)@enwikinews Philippe (WMF)@enwikiquote Philippe (WMF)@enwikisource Philippe (WMF)@enwikiversity Philippe (WMF)@enwikivoyage Philippe (WMF)@eswiki Philippe (WMF)@fawiki Philippe (WMF)@frwiki Philippe (WMF)@frwiktionary Philippe (WMF)@itwiki Philippe (WMF)@loginwiki Philippe (WMF)@mediawikiwiki Philippe (WMF)@metawiki Philippe (WMF)@mgwiktionary Philippe (WMF)@nlwiki Philippe (WMF)@simplewiki Philippe (WMF)@wikidatawiki Philippe (WMF)@xhwiki Philippe (WMF)@zhwiki Philippe@enwiki Piku@frwiki Pmlineditor@loginwiki Polimerek@plwiki Polimerek@plwikimedia Ponyo@enwiki Postrach@cswiki Poudou!@frwiki Pouyana@fawiki Premeditated Chaos@enwiki Primefac@enwiki Prométhée@frwiki Ptjackyll@plwiki Pundit@plwiki PurpleBuffalo@hewiki Putnik@ruwiki Q-bit array@ruwiki Queix@frwiki RLuts@ukwiki RadiX@bhwiki RadiX@cswikisource RadiX@enwikiversity RadiX@hiwiki RadiX@loginwiki RadiX@pswiki RadiX@ptwiki RadiX@ptwikibooks RadiX@ptwikiquote RadiX@svwikiversity Rastrojo@eswiki Raul654@enwiki Rax@dewiki Razimantv@mlwiki Rdsmith4@arwiki Rdsmith4@biwiki Rdsmith4@etwiki Rdsmith4@fawiki Rdsmith4@gawiki Rdsmith4@huwiki Rdsmith4@idwiki Rdsmith4@jawiki Rdsmith4@kowiki Rdsmith4@lmowiki Rdsmith4@lvwiki Rdsmith4@metawiki Rdsmith4@newiktionary Rdsmith4@nrmwiki Rdsmith4@rowiki Rdsmith4@rowiktionary Rdsmith4@scowiki Rdsmith4@trwiki Rdsmith4@zhwiki ReAl@ukwiki Reaper Eternal@enwiki Redux@enwiki Reedy (WMF)@arwiki Reedy (WMF)@enwiki Reedy (WMF)@mediawikiwiki Reedy (WMF)@metawiki Reedy (WMF)@testwiki Reedy@commonswiki Reedy@enwiki Reedy@itwiki Reedy@labswiki Reedy@wikidatawiki Rei-artur@ptwiki Renamed user mou89p43twvqcvm8ut9w3@enwiki Revi C.@betawikiversity Revi C.@dewikiquote Revi C.@enwiki Revi C.@enwikinews Revi C.@enwikivoyage Revi C.@eswikinews Revi C.@kowiktionary Revi C.@loginwiki Revi C.@mediawikiwiki Revi C.@rowiki Revi C.@ruwikinews Revi C.@ruwikiquote Revi C.@smnwiki Revi C.@zh_classicalwiki Revi C.@zhwiki Rexcornot@frwiki Reza1615@fawiki RiazACU@bnwiki Richwales@enwiki Richwales@eswiki RickinBaltimore@enwiki Risker@enwiki Rlevse@enwiki RobLa-WMF@enwiki RobLa-WMF@metawiki RobLa-WMF@plwiki Rodasmith@enwiktionary Roger Davies@enwiki Rojelio@itwiki Romihaitza@simplewiki Romihaitza@yiwiki RoySmith@arwiki RoySmith@enwiki RoySmith@metawiki Rsocol@rowiki Rubin16@ruwiki Ruslik0@loginwiki Ruslik0@nlwikibooks Ruthven@itwiki Ruthven@testwiki Ruy Pugliesi@mediawikiwiki Rxy@loginwiki Rxy@wikidatawiki Ryan Kaldari (WMF)@commonswiki Ryan Kaldari (WMF)@enwiki Ryan Lane (WMF)@labswiki SB Johnny@commonswiki SB Johnny@enwikibooks SB Johnny@enwikiversity SBJohnny@enwikibooks SBassett (WMF)@enwiki SBassett (WMF)@mediawikiwiki SBassett (WMF)@metawiki SBassett (WMF)@testwiki SEPRodrigues@ptwiki SHB2000@enwikivoyage SHB2000@loginwiki SMP@cawiki SNg (WMF)@enwiki SNg (WMF)@metawiki SNg (WMF)@nlwiki SPoore (WMF)@enwiki SPoore (WMF)@hrwiki SPoore (WMF)@itwiki SPoore (WMF)@metawiki SPoore (WMF)@simplewiki SPoore (WMF)@testwiki SQL@enwiki SSandu-WMF@enwiki SSpalding (WMF)@enwiki ST47@enwiki ST47@testwiki Sadrettin@trwiki Sahehco@fawiki Sahehco@testwiki Sakretsu@itwiki Sakretsu@loginwiki Salvio giuliano@enwiki Sam Korn@enwiki Samuel (WMF)@enwiki Samuel (WMF)@eswiki Samuel (WMF)@frwiki Samuel (WMF)@iawiki Samuel (WMF)@metawiki Samuel (WMF)@testwiki Samuel (WMF)@ukwiki Saper@plwiki Savh@itwikibooks Savh@loginwiki Sbisolo@itwiki Schiste@frwiki Schlum@frwiki Schniggendiller@loginwiki ScottishFinnishRadish@enwiki Sdrqaz@enwiki SelfieCity@enwikivoyage Seraphimblade@enwiki Shanel@aawiki Shanel@arwiki Shanel@cswiki Shanel@cswikiquote Shanel@eewiki Shanel@elwikibooks Shanel@enwiki Shanel@enwikiquote Shanel@eswikisource Shanel@fawiki Shanel@frwikinews Shanel@frwiktionary Shanel@igwiki Shanel@itwikinews Shanel@jawikinews Shanel@kowiki Shanel@mlwiki Shanel@ndswiki Shanel@ptwiki Shanel@ptwikinews Shanel@ptwiktionary Shanel@simplewiktionary Shanel@skwiki Shanel@skwiktionary Shanel@tiwiki Shanel@ukwiki Shanel@vecwiki Shanel@yiwiki Shanel@zhwiki Shanmugamp7@enwiki Shanmugamp7@loginwiki Shell Kinney@enwiki Shivanarayana@itwiki Shizhao@betawikiversity Shizhao@commonswiki Shizhao@dewiki Shizhao@enwiki Shizhao@frwiki Shizhao@kowiki Shizhao@kowikisource Shizhao@metawiki Shizhao@ptwikiquote Shizhao@ruwiki Shizhao@viwiki Shizhao@zhwiki Siam2019@thwiki SilkTork@enwiki SilverLocust@enwiki Sir Lestaty de Lioncourt@ptwikinews Sir kiss@hewiki Sir48@commonswiki Sir48@dawiki SirFozzie@enwiki Sjoerddebruin@loginwiki Skenmy@enwikinews Slaporte (WMF)@commonswiki Slaporte (WMF)@enwiki Slaporte (WMF)@ptwiki Smooth O@bswiki Snowdog@itwiki SoWhy@enwiki Sotiale@enwiki Sotiale@kowiki Sotiale@loginwiki Sotiale@metawiki Sotiale@wikidatawiki Spacebirdy@anwiki Spacebirdy@cawiki Spacebirdy@metawiki Spacebirdy@testwiki Spangineer@enwikisource SpeedyGonsales@hrwiki Spicy@enwiki Squasher@dewiki Stanglavine@loginwiki Stanglavine@metawiki Stanglavine@ptwiki Stephen Bain@enwiki Steve Smith@enwiki Stigfinnare@svwiki Strainu@rowiki Stryn@fiwiki Stryn@loginwiki Stwalkerster@enwiki Suisui@jawiki Superpes15@enwiki Superpes15@enwikiquote Superpes15@fawiki Superpes15@itwiki Superpes15@itwikiversity Superpes15@loginwiki Superpes15@metawiki Superspritz@itwiki Surjection@enwiktionary Syp@huwiki Szwedzki@plwiki TBolliger (WMF)@testwiki THargrove (WMF)@plwiki Tarawneh@arwiki Taweetham@thwiki Tbone@fiwiki Tegel@enwiki Tegel@eswiktionary Tegel@loginwiki Tegel@metawiki Tegel@svwiki Teles@enwiki Teles@loginwiki Teles@metawiki Teles@ptwiki Tfinc@enwiki Tfinc@metawiki Tgr (WMF)@commonswiki Tgr (WMF)@enwiki Tgr (WMF)@loginwiki Tgr (WMF)@metawiki Tgr (WMF)@testwiki Tgr (WMF)@trwiki Tgr@huwiki Thatcher@enwiki The Epopt@enwiki The Rambling Man@simplewiki The Squirrel Conspiracy@commonswiki TheDaveRoss@enwiktionary TheStriker@hewiki Theghaz@dewiki Theleekycauldron@enwiki Thenub314@enwikibooks TheresNoTime@enwiki TheresNoTime@loginwiki TheresNoTime@simplewiki Thogo@afwiki Thogo@alswiki Thogo@arzwiki Thogo@azwiktionary Thogo@barwiki Thogo@bat_smgwiki Thogo@bawiki Thogo@bgwiki Thogo@biwiki Thogo@brwiki Thogo@diqwiki Thogo@eewiki Thogo@elwiktionary Thogo@emlwiki Thogo@enwikisource Thogo@enwikiversity Thogo@enwiktionary Thogo@eowiki Thogo@eswiktionary Thogo@euwiki Thogo@euwiktionary Thogo@fawiki Thogo@fowiktionary Thogo@frwikinews Thogo@frwikiquote Thogo@fywiki Thogo@gawiki Thogo@gdwiktionary Thogo@gvwiktionary Thogo@hiwiki Thogo@hrwiki Thogo@huwiki Thogo@hywiki Thogo@itwikiquote Thogo@itwiktionary Thogo@iuwiki Thogo@jawikinews Thogo@jawikisource Thogo@jawiktionary Thogo@kmwiki Thogo@kowiki Thogo@liwiki Thogo@mediawikiwiki Thogo@miwiki Thogo@ndswiki Thogo@nnwiki Thogo@nowiki Thogo@pamwiki Thogo@ptwikinews Thogo@quwiki Thogo@rowiki Thogo@simplewiktionary Thogo@siwiki Thogo@sourceswiki Thogo@sowiki Thogo@specieswiki Thogo@stqwiki Thogo@vecwiki Thogo@viwiki Thogo@vowiki Thogo@wawiki Thogo@xhwiki Thogo@yiwiki Thogo@zhwiki Thryduulf@enwiki Tim Starling (WMF)@dewiki Tim Starling (WMF)@enwiki Tim Starling (WMF)@mediawikiwiki Tim Starling (WMF)@ruwiki Tim Starling@enwiki Tim Starling@labswiki Tim Starling@metawiki Tim Starling@testwiki Timekeepertmk@thwiki Timotheus Canens@enwiki Tinz@dewiki Tiptoety@commonswiki Tiptoety@enwiki Tiptoety@metawiki Titore@itwiki Tks4Fish@enwiki Tks4Fish@loginwiki Tks4Fish@ptwiki Tnxman307@enwiki ToBeFree@enwiki Tom Morris@enwikinews Tomer T@hewiki TonyBallioni@enwiki Toto Azéro@frwiki Triglav@jawiki Trijnstel@commonswiki Trijnstel@enwiki Trijnstel@loginwiki Trijnstel@metawiki TunnelESON@enwikiquote TunnelESON@fawiki TunnelESON@frwiktionary TunnelESON@jawikibooks TunnelESON@jawiktionary TunnelESON@kowiki TunnelESON@kowikisource TunnelESON@metawiki TunnelESON@mlwiki TunnelESON@ptwiki TunnelESON@ruwiki TunnelESON@siwiki TunnelESON@thwiki TunnelESON@ukwiki TunnelESON@viwiki TunnelESON@zhwiki Tznkai@enwiki Ugur Basak@trwiki Uncitoyen@trwiki UninvitedCompany@enwiki Ustad abu gosok@idwiki VIGNERON@loginwiki VWalters-WMF@enwiki VWalters-WMF@testwiki Vanamonde93@enwiki Vanished user 5zariu3jisj0j4irj@enwiki Vassyana@enwiki Vermont@loginwiki Vermont@metawiki Vermont@simplewiki Versageek@enwiki Versageek@enwiktionary VictorAnyakin@ukwiki Vigorous action@jawiki Vincent Vega@trwiki Viniciusmc@ptwiki Vituzzu@enwiki Vituzzu@eswikiquote Vituzzu@itwiki Vituzzu@metawiki Vituzzu@ptwikibooks Vlad@rowiki Vodomar@hrwiki W.CC@jawiki WBrown (WMF)@test2wiki WBrown (WMF)@testcommonswiki WBrown (WMF)@testwiki WMFOffice@arwiki WMFOffice@commonswiki WMFOffice@enwiki WMFOffice@fawiki WMFOffice@frwiki WMFOffice@loginwiki WMFOffice@mediawikiwiki WMFOffice@metawiki WMFOffice@ruwiki WMFOffice@wikidatawiki Wagino 20100516@idwiki Walter@brwiki Walter@nlwiki Walter@nlwikibooks Walter@testwiki WarX@plwiki Wegge@dawiki Werdna@enwiki Werdna@enwikisource Werdna@mediawikiwiki Whiteknight@enwikibooks Wiki13@loginwiki Wikimol@cswiki Wikitanvir@bnwiki WilliamH@enwiki Wim b@loginwiki Wind@ruwiki Wizardman@enwiki Wojciech Pędzich@betawikiversity Wojciech Pędzich@commonswiki Wojciech Pędzich@dewiktionary Wojciech Pędzich@eewiki Wojciech Pędzich@enwikiquote Wojciech Pędzich@enwikiversity Wojciech Pędzich@fawiki Wojciech Pędzich@fiwikibooks Wojciech Pędzich@itwikinews Wojciech Pędzich@kowiki Wojciech Pędzich@ltwiktionary Wojciech Pędzich@nlwikimedia Wojciech Pędzich@nowiki Wojciech Pędzich@outreachwiki Wojciech Pędzich@plwiki Wojciech Pędzich@plwikibooks Wojciech Pędzich@plwikimedia Wojciech Pędzich@plwikinews Wojciech Pędzich@plwikiquote Wojciech Pędzich@plwiktionary Wojciech Pędzich@rowiki Wojciech Pędzich@simplewiktionary Wojciech Pędzich@szlwiki Wojciech Pędzich@ukwiki Wojciech Pędzich@xhwiki Worm That Turned@enwiki Wugapodes@enwiki Wulfson@ruwiki XXBlackburnXx@enwiki XXBlackburnXx@loginwiki XXBlackburnXx@nlwiki Xania@enwikibooks Xaosflux@loginwiki Xaosflux@metawiki Xeno@enwiki Y-dash@jawiki Yahya@loginwiki Yamla@enwiki Yann@arwiki Yann@commonswiki Yann@enwikisource Yann@frwikinews Yann@hiwiki Yann@itwikiversity Yann@metawiki Yann@sourceswiki Yann@ukwiki YellowMonkey@enwiki Yunshui@enwiki Z1720@enwiki ZExley (WMF)@enwiki ZSoo (WMF)@enwiki Zabe@enwiki Zabe@mediawikiwiki Zabe@test2wiki Zabe@testcommonswiki Zabe@testwiki Zafer@trwikimedia Zzuuzz@enwiki Érico@ptwiki Боки@srwiki Ле Лой@ruwiki Обрадовић Горан@srwiki Стефанко1982@ukwiki בריאן@hewiki דגש@hewiki דולב@hewiki דניאל ב.@hewiki יונה בנדלאק@hewiki ירון@hewiki מוטי@hewiki מתניה@zhwiki עוזי ו.@hewiki עידו@hewiki עli@hewiki רחל1@hewiki باسم@arwiki جار الله@arwiki صالح@arwiki علاء@arwiki علاء@enwiki علاء@loginwiki علاء@wikidatawiki فيصل@arwiki ميموني@arwiki আফতাবuজ্জামান@bnwiki さかおり@jawiki 柏尾菓子@jawiki 欅@jawiki 海獺@jawiki 이강철@kowiki `; // PASTE YOUR LIST HERE let siteMap = {}; // List of wikis where CheckUser is disabled const disabledCUWikis = [ 'aawikibooks', 'abwiktionary', 'advisorywiki', 'akwiktionary', 'angwikiquote', 'angwikisource', 'astwikibooks', 'astwikiquote', 'aswikibooks', 'aswiktionary', 'avwiktionary', 'aywikibooks', 'bhwiktionary', 'biwikibooks', 'biwiktionary', 'bmwikibooks', 'bmwikiquote', 'bmwiktionary', 'bowiktionary', 'chwikibooks', 'chwiktionary', 'cnwikimedia', 'crwikiquote', 'crwiktionary', 'dzwiktionary', 'gawikibooks', 'gnwikibooks', 'gotwikibooks', 'guwikibooks', 'htwikisource', 'huwikinews', 'hzwiki', 'iewikibooks', 'iiwiki', 'internalwiki', 'kjwiki', 'kkwikiquote', 'knwikibooks', 'krwiki', 'krwikiquote', 'kswikibooks', 'kswikiquote', 'kwwikiquote', 'lbwikibooks', 'lbwikiquote', 'lnwikibooks', 'lvwikibooks', 'mhwiktionary', 'mnwikibooks', 'muswiki', 'nahwikibooks', 'nawikibooks', 'nawikiquote', 'ndswikibooks', 'ndswikiquote', 'piwiktionary', 'pswikibooks', 'quwikibooks', 'quwikiquote', 'rmwikibooks', 'rmwiktionary', 'rnwiktionary', 'scwiktionary', 'searchcomwiki', 'snwiktionary', 'spcomwiki', 'swwikibooks', 'thwikinews', 'tkwikibooks', 'tkwikiquote', 'towiktionary', 'transitionteamwiki', 'trwikinews', 'ttwikiquote', 'twwiktionary', 'ugwikibooks', 'ugwikiquote', 'vowikibooks', 'vowikiquote', 'wawikibooks', 'wikimania2005wiki', 'wikimania2006wiki', 'wikimania2007wiki', 'wikimania2009wiki', 'wikimania2015wiki', 'wowikiquote', 'xhwikibooks', 'xhwiktionary', 'yowikibooks', 'yowiktionary', 'zawikibooks', 'zawikiquote', 'zawiktionary', 'zh_min_nanwikibooks', 'zuwikibooks', 'aawiktionary', 'akwikibooks', 'amwikiquote', 'angwikibooks', 'bgwikinews', 'bowikibooks', 'chowiki', 'cowikibooks', 'cowikiquote', 'gawikiquote', 'howiki', 'ikwiktionary', 'kywikibooks', 'mhwiki', 'miwikibooks', 'mywikibooks', 'nawiki', 'nzwikimedia', 'pa_uswikimedia', 'qualitywiki', 'ruwikimedia', 'sdwikinews', 'sewikibooks', 'simplewikibooks', 'strategywiki', 'suwikibooks', 'tenwiki', 'usabilitywiki', 'uzwikibooks', 'wikimania2010wiki', 'wikimania2011wiki', 'wikimania2012wiki', 'wikimania2013wiki', 'wikimania2014wiki', 'wikimania2016wiki', 'wikimania2017wiki', 'wikimania2018wiki', 'zh_min_nanwikiquote' ]; async function loadSiteMatrix() { const api = new mw.Api(); try { const data = await api.get({ action: 'sitematrix', format: 'json', smtype: 'language|special', smlangprop: 'site', smsiteprop: 'dbname|url', formatversion: 2, origin: '*' }); const matrix = data.sitematrix; Object.keys(matrix).forEach(key => { const entry = matrix[key]; if (entry.site && Array.isArray(entry.site)) { entry.site.forEach(site => { siteMap[site.dbname] = site.url.replace(/^https?:\/\//, ''); }); } }); if (matrix.specials) { matrix.specials.forEach(special => { siteMap[special.dbname] = special.url.replace(/^https?:\/\//, ''); }); } return true; } catch (e) { return false; } } function initInterface() { document.title = "GCUS Data Generator"; $('#mw-content-text').empty().append(` <div style="background:white; border:1px solid #a2a9b1; padding:20px; font-family:sans-serif; color:#202122;"> <h1 style="margin-top:0; border-bottom:1px solid #a2a9b1;">GCUS Data Generator v7.0</h1> <p>Engine: <b>GlobalCheckUserStats (GCUS) Integrated</b>. <br> Format: <b>YYYY-MM-DD</b>. Filter: <b>CU < 60 days ignored</b>.</p> <div style="display:flex; gap:15px; margin-bottom:20px;"> <div style="flex:1;"> <strong>Input List (name@dbname):</strong> <textarea id="gcusInput" style="width:100%; height:180px; font-family:monospace; font-size:12px; border:1px solid #a2a9b1; margin-top:5px;">${USERS_LIST}</textarea> </div> <div style="flex:1;"> <strong>Log:</strong> <div id="gcusLog" style="height:180px; overflow-y:scroll; background:black; color:#0f0; font-family:monospace; font-size:11px; padding:5px; border-radius:3px; margin-top:5px;">Ready...</div> </div> <div style="width:220px;"> <strong>Skipped/Failed:</strong> <textarea id="gcusFailed" readonly style="width:100%; height:180px; font-family:monospace; font-size:11px; background:#fff0f0; border:1px solid #fcc; margin-top:5px; color:#a00;"></textarea> </div> </div> <button id="gcusStart" style="padding:10px 24px; background:#36c; color:white; border:none; cursor:pointer; font-weight:bold; border-radius:2px;">START SCAN</button> <span id="gcusStatus" style="margin-left:15px; font-weight:bold;"></span> <div id="gcusResultArea" style="display:none; margin-top:20px;"> <strong>Lua Output (Module:GlobalRoles/Data):</strong> <textarea id="gcusOutput" style="width:100%; height:350px; font-family:monospace; font-size:12px; background:#fffbe6; border:1px solid #f2e394; margin-top:5px;"></textarea> </div> </div> `); $('#gcusStart').on('click', runGenerator); } async function runGenerator() { const $btn = $('#gcusStart'), $log = $('#gcusLog'), $status = $('#gcusStatus'), $failed = $('#gcusFailed'); const input = $('#gcusInput').val(); $btn.prop('disabled', true).css('background', '#a2a9b1').text('PREPARING...'); $log.empty(); $failed.val(''); const log = (msg) => { $log.append($('<div>').text(`[${new Date().toLocaleTimeString()}] ${msg}`)); $log.scrollTop($log[0].scrollHeight); }; log("Loading SiteMatrix mapping..."); await loadSiteMatrix(); log("Mapping ready."); $('#gcusResultArea').show(); const sleep = ms => new Promise(r => setTimeout(r, ms)); async function robustFetch(url, wikiName, attempts = 0) { await sleep(600); // Fair-use delay const fullUrl = url + "&origin=*&contact=User:MrJaroslavik"; try { const response = await fetch(fullUrl); if (response.status === 429) { const retry = parseInt(response.headers.get('Retry-After')) || (30 * (attempts + 1)); log(`!! Rate limit on ${wikiName}. Waiting ${retry}s...`); await sleep(retry * 1000); return robustFetch(url, wikiName, attempts); } if (!response.ok) throw new Error(`HTTP ${response.status}`); return await response.json(); } catch (e) { if (attempts < 2) { log(`Retrying ${wikiName} (${attempts + 1}/3)...`); await sleep(2000); return robustFetch(url, wikiName, attempts + 1); } log(`!! Skipped project: ${wikiName}`); if (!$failed.val().includes(wikiName)) $failed.val($failed.val() + wikiName + ' (failed)\n'); return null; } } function checkChange(logEntry, roleName) { const p = logEntry.params || {}; const r = roleName.toLowerCase(); const normalize = (v) => Array.isArray(v) ? v.join(',').toLowerCase() : String(v || "").toLowerCase(); const oldStr = normalize(p.oldGroups || p.oldgroups || p.remove || p[0] || p["0"]); const newStr = normalize(p.newGroups || p.newgroups || p.add || p[1] || p["1"]); const hasRole = (s) => new RegExp('(^|,|\\s)' + r + '($|,|\\s)').test(s); if (!hasRole(oldStr) && hasRole(newStr)) return "added"; if (hasRole(oldStr) && !hasRole(newStr)) return "removed"; return null; } function getDaysDiff(dateFrom, dateTo) { if (dateTo === 'present') return 999; return Math.floor((new Date(dateTo) - new Date(dateFrom)) / 86400000); } const localArray = input.split('\n').map(s => s.trim()).filter(s => s.length > 0); const globalArray = [...new Set(localArray.map(s => { const parts = s.split('@'); parts.pop(); return parts.join('@'); }))]; let rawData = {}; log(`Scanning ${globalArray.length} unique accounts.`); // 1. GLOBAL ROLES for (let i = 0; i < globalArray.length; i++) { const name = globalArray[i]; $status.text(`Global: ${i+1}/${globalArray.length} (${name})`); let continueToken = ''; while (continueToken !== undefined) { const data = await robustFetch(`https://meta.wikimedia.org/w/api.php?action=query&list=logevents&letype=gblrights&letitle=User:${encodeURIComponent(name)}&lelimit=max${continueToken}&format=json`, 'metawiki'); if (!data) break; const logs = (data.query && data.query.logevents ? data.query.logevents : []).reverse(); logs.forEach(l => { const date = l.timestamp.substring(0, 10); ['steward', 'staff', 'ombuds'].forEach(role => { const change = checkChange(l, role); if (change === "added") { if (!rawData[name]) rawData[name] = []; rawData[name].push({ role, db: 'global', from: date, to: 'present' }); } else if (change === "removed") { const entry = (rawData[name] || []).filter(e => e.role === role && e.to === 'present').pop(); if (entry) entry.to = date; } }); }); continueToken = data.continue ? `&lecontinue=${data.continue.lecontinue}` : undefined; } } // 2. LOCAL ROLES for (let i = 0; i < localArray.length; i++) { const [name, db] = [localArray[i].split('@').slice(0,-1).join('@'), localArray[i].split('@').pop()]; // Skip disabled wikis if (disabledCUWikis.includes(db)) { if (!$failed.val().includes(db)) $failed.val($failed.val() + db + ' (CU disabled)\n'); continue; } if (db === 'loginwiki' && (rawData[name] || []).some(e => e.db === 'global' && e.role === 'steward')) continue; $status.text(`Local: ${i+1}/${localArray.length} (${name}@${db})`); const domain = siteMap[db] || `${db.replace('wiki', '').replace(/_/g, '-')}.wikipedia.org`; let continueToken = ''; try { while (continueToken !== undefined) { const data = await robustFetch(`https://${domain}/w/api.php?action=query&list=logevents&letype=rights&letitle=User:${encodeURIComponent(name)}&lelimit=max${continueToken}&format=json`, db); if (!data) break; const logs = (data.query && data.query.logevents ? data.query.logevents : []).reverse(); logs.forEach(l => { const date = l.timestamp.substring(0, 10); const p = l.params || {}; // Získání expiry z metadat (pokud existuje) let expiryDate = null; const meta = p.newmetadata || p.newMetadata || []; const metaArray = Array.isArray(meta) ? meta : Object.values(meta); const cuMeta = metaArray.find(m => m && (m.group === 'checkuser' || m.group === 'check user')); if (cuMeta && cuMeta.expiry && cuMeta.expiry !== 'infinity') { expiryDate = cuMeta.expiry.substring(0, 10); } const change = checkChange(l, 'checkuser'); if (change === "added") { if (!rawData[name]) rawData[name] = []; // Pokud má záznam expiry, použijeme ho rovnou jako 'to' const toValue = expiryDate ? expiryDate : 'present'; rawData[name].push({ role: 'cu', db: db, from: date, to: toValue }); } else if (change === "removed") { const entry = (rawData[name] || []).filter(e => e.role === 'cu' && e.db === db && e.to === 'present').pop(); if (entry) entry.to = date; } }); continueToken = data.continue ? `&lecontinue=${data.continue.lecontinue}` : undefined; } } catch(e) { log(`Error ${name}@${db}: ${e.message}`); } } // 3. FINAL LUA let lua = "return {\n users = {\n"; const sortedUsers = Object.keys(rawData).sort(); for (const user of sortedUsers) { const filtered = rawData[user].filter(e => e.db === 'global' || e.to === 'present' || getDaysDiff(e.from, e.to) >= 60); if (filtered.length > 0) { lua += ` ["${user}"] = {\n`; filtered.forEach(e => { lua += ` { role = "${e.role}", db = "${e.db}", from = "${e.from}", to = "${e.to}" },\n`; }); lua += ` },\n`; } } lua += " }\n}"; $('#gcusOutput').val(lua); $status.text("FINISHED!"); log("Scan complete."); $btn.prop('disabled', false).css('background', '#36c').text('RUN AGAIN'); } $(initInterface); })(); /* </nowiki> */ 4af7rs7ypqk9skuag03be3y5pqo3fwm User:Enbi/test2.js 2 175056 739793 2026-04-29T20:16:00Z Enbi 72574 doubt this will work (preact) 739793 javascript text/javascript (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e,t,n,r,i,a,o,s,c,l,u,d,f,p,m={},h=[],g=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,_=Array.isArray;function v(e,t){for(var n in t)e[n]=t[n];return e}function y(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function b(t,n,r){var i,a,o,s={};for(o in n)o==`key`?i=n[o]:o==`ref`?a=n[o]:s[o]=n[o];if(arguments.length>2&&(s.children=arguments.length>3?e.call(arguments,2):r),typeof t==`function`&&t.defaultProps!=null)for(o in t.defaultProps)s[o]===void 0&&(s[o]=t.defaultProps[o]);return x(t,s,i,a,null)}function x(e,r,i,a,o){var s={type:e,props:r,key:i,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o??++n,__i:-1,__u:0};return o==null&&t.vnode!=null&&t.vnode(s),s}function S(e){return e.children}function C(e,t){this.props=e,this.context=t}function w(e,t){if(t==null)return e.__?w(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type==`function`?w(e):null}function T(e){if(e.__P&&e.__d){var n=e.__v,r=n.__e,i=[],a=[],o=v({},n);o.__v=n.__v+1,t.vnode&&t.vnode(o),F(e.__P,o,n,e.__n,e.__P.namespaceURI,32&n.__u?[r]:null,i,r??w(n),!!(32&n.__u),a),o.__v=n.__v,o.__.__k[o.__i]=o,L(i,o,a),n.__e=n.__=null,o.__e!=r&&E(o)}}function E(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),E(e)}function D(e){(!e.__d&&(e.__d=!0)&&r.push(e)&&!O.__r++||i!=t.debounceRendering)&&((i=t.debounceRendering)||a)(O)}function O(){try{for(var e,t=1;r.length;)r.length>t&&r.sort(o),e=r.shift(),t=r.length,T(e)}finally{r.length=O.__r=0}}function ee(e,t,n,r,i,a,o,s,c,l,u){var d,f,p,g,_,v,y,b=r&&r.__k||h,x=t.length;for(c=k(n,t,b,c,x),d=0;d<x;d++)(p=n.__k[d])!=null&&(f=p.__i!=-1&&b[p.__i]||m,p.__i=d,v=F(e,p,f,i,a,o,s,c,l,u),g=p.__e,p.ref&&f.ref!=p.ref&&(f.ref&&z(f.ref,null,p),u.push(p.ref,p.__c||g,p)),_==null&&g!=null&&(_=g),(y=!!(4&p.__u))||f.__k===p.__k?(c=A(p,c,e,y),y&&f.__e&&(f.__e=null)):typeof p.type==`function`&&v!==void 0?c=v:g&&(c=g.nextSibling),p.__u&=-7);return n.__e=_,c}function k(e,t,n,r,i){var a,o,s,c,l,u=n.length,d=u,f=0;for(e.__k=Array(i),a=0;a<i;a++)(o=t[a])!=null&&typeof o!=`boolean`&&typeof o!=`function`?(typeof o==`string`||typeof o==`number`||typeof o==`bigint`||o.constructor==String?o=e.__k[a]=x(null,o,null,null,null):_(o)?o=e.__k[a]=x(S,{children:o},null,null,null):o.constructor===void 0&&o.__b>0?o=e.__k[a]=x(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):e.__k[a]=o,c=a+f,o.__=e,o.__b=e.__b+1,s=null,(l=o.__i=j(o,n,c,d))!=-1&&(d--,(s=n[l])&&(s.__u|=2)),s==null||s.__v==null?(l==-1&&(i>u?f--:i<u&&f++),typeof o.type!=`function`&&(o.__u|=4)):l!=c&&(l==c-1?f--:l==c+1?f++:(l>c?f--:f++,o.__u|=4))):e.__k[a]=null;if(d)for(a=0;a<u;a++)(s=n[a])!=null&&!(2&s.__u)&&(s.__e==r&&(r=w(s)),B(s,s));return r}function A(e,t,n,r){var i,a;if(typeof e.type==`function`){for(i=e.__k,a=0;i&&a<i.length;a++)i[a]&&(i[a].__=e,t=A(i[a],t,n,r));return t}e.__e!=t&&(r&&(t&&e.type&&!t.parentNode&&(t=w(e)),n.insertBefore(e.__e,t||null)),t=e.__e);do t&&=t.nextSibling;while(t!=null&&t.nodeType==8);return t}function j(e,t,n,r){var i,a,o,s=e.key,c=e.type,l=t[n],u=l!=null&&(2&l.__u)==0;if(l===null&&s==null||u&&s==l.key&&c==l.type)return n;if(r>+!!u){for(i=n-1,a=n+1;i>=0||a<t.length;)if((l=t[o=i>=0?i--:a++])!=null&&!(2&l.__u)&&s==l.key&&c==l.type)return o}return-1}function M(e,t,n){t[0]==`-`?e.setProperty(t,n??``):e[t]=n==null?``:typeof n!=`number`||g.test(t)?n:n+`px`}function N(e,t,n,r,i){var a,o;n:if(t==`style`)if(typeof n==`string`)e.style.cssText=n;else{if(typeof r==`string`&&(e.style.cssText=r=``),r)for(t in r)n&&t in n||M(e.style,t,``);if(n)for(t in n)r&&n[t]==r[t]||M(e.style,t,n[t])}else if(t[0]==`o`&&t[1]==`n`)a=t!=(t=t.replace(u,`$1`)),o=t.toLowerCase(),t=o in e||t==`onFocusOut`||t==`onFocusIn`?o.slice(2):t.slice(2),e.l||={},e.l[t+a]=n,n?r?n[l]=r[l]:(n[l]=d,e.addEventListener(t,a?p:f,a)):e.removeEventListener(t,a?p:f,a);else{if(i==`http://www.w3.org/2000/svg`)t=t.replace(/xlink(H|:h)/,`h`).replace(/sName$/,`s`);else if(t!=`width`&&t!=`height`&&t!=`href`&&t!=`list`&&t!=`form`&&t!=`tabIndex`&&t!=`download`&&t!=`rowSpan`&&t!=`colSpan`&&t!=`role`&&t!=`popover`&&t in e)try{e[t]=n??``;break n}catch{}typeof n==`function`||(n==null||!1===n&&t[4]!=`-`?e.removeAttribute(t):e.setAttribute(t,t==`popover`&&n==1?``:n))}}function P(e){return function(n){if(this.l){var r=this.l[n.type+e];if(n[c]==null)n[c]=d++;else if(n[c]<r[l])return;return r(t.event?t.event(n):n)}}}function F(e,n,r,i,a,o,s,c,l,u){var d,f,p,m,g,b,x,w,T,E,D,O,k,A,j,M=n.type;if(n.constructor!==void 0)return null;128&r.__u&&(l=!!(32&r.__u),o=[c=n.__e=r.__e]),(d=t.__b)&&d(n);n:if(typeof M==`function`)try{if(w=n.props,T=M.prototype&&M.prototype.render,E=(d=M.contextType)&&i[d.__c],D=d?E?E.props.value:d.__:i,r.__c?x=(f=n.__c=r.__c).__=f.__E:(T?n.__c=f=new M(w,D):(n.__c=f=new C(w,D),f.constructor=M,f.render=ne),E&&E.sub(f),f.state||={},f.__n=i,p=f.__d=!0,f.__h=[],f._sb=[]),T&&f.__s==null&&(f.__s=f.state),T&&M.getDerivedStateFromProps!=null&&(f.__s==f.state&&(f.__s=v({},f.__s)),v(f.__s,M.getDerivedStateFromProps(w,f.__s))),m=f.props,g=f.state,f.__v=n,p)T&&M.getDerivedStateFromProps==null&&f.componentWillMount!=null&&f.componentWillMount(),T&&f.componentDidMount!=null&&f.__h.push(f.componentDidMount);else{if(T&&M.getDerivedStateFromProps==null&&w!==m&&f.componentWillReceiveProps!=null&&f.componentWillReceiveProps(w,D),n.__v==r.__v||!f.__e&&f.shouldComponentUpdate!=null&&!1===f.shouldComponentUpdate(w,f.__s,D)){n.__v!=r.__v&&(f.props=w,f.state=f.__s,f.__d=!1),n.__e=r.__e,n.__k=r.__k,n.__k.some(function(e){e&&(e.__=n)}),h.push.apply(f.__h,f._sb),f._sb=[],f.__h.length&&s.push(f);break n}f.componentWillUpdate!=null&&f.componentWillUpdate(w,f.__s,D),T&&f.componentDidUpdate!=null&&f.__h.push(function(){f.componentDidUpdate(m,g,b)})}if(f.context=D,f.props=w,f.__P=e,f.__e=!1,O=t.__r,k=0,T)f.state=f.__s,f.__d=!1,O&&O(n),d=f.render(f.props,f.state,f.context),h.push.apply(f.__h,f._sb),f._sb=[];else do f.__d=!1,O&&O(n),d=f.render(f.props,f.state,f.context),f.state=f.__s;while(f.__d&&++k<25);f.state=f.__s,f.getChildContext!=null&&(i=v(v({},i),f.getChildContext())),T&&!p&&f.getSnapshotBeforeUpdate!=null&&(b=f.getSnapshotBeforeUpdate(m,g)),A=d!=null&&d.type===S&&d.key==null?R(d.props.children):d,c=ee(e,_(A)?A:[A],n,r,i,a,o,s,c,l,u),f.base=n.__e,n.__u&=-161,f.__h.length&&s.push(f),x&&(f.__E=f.__=null)}catch(e){if(n.__v=null,l||o!=null)if(e.then){for(n.__u|=l?160:128;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;o[o.indexOf(c)]=null,n.__e=c}else{for(j=o.length;j--;)y(o[j]);I(n)}else n.__e=r.__e,n.__k=r.__k,e.then||I(n);t.__e(e,n,r)}else o==null&&n.__v==r.__v?(n.__k=r.__k,n.__e=r.__e):c=n.__e=te(r.__e,n,r,i,a,o,s,l,u);return(d=t.diffed)&&d(n),128&n.__u?void 0:c}function I(e){e&&(e.__c&&(e.__c.__e=!0),e.__k&&e.__k.some(I))}function L(e,n,r){for(var i=0;i<r.length;i++)z(r[i],r[++i],r[++i]);t.__c&&t.__c(n,e),e.some(function(n){try{e=n.__h,n.__h=[],e.some(function(e){e.call(n)})}catch(e){t.__e(e,n.__v)}})}function R(e){return typeof e!=`object`||!e||e.__b>0?e:_(e)?e.map(R):v({},e)}function te(n,r,i,a,o,s,c,l,u){var d,f,p,h,g,v,b,x=i.props||m,S=r.props,C=r.type;if(C==`svg`?o=`http://www.w3.org/2000/svg`:C==`math`?o=`http://www.w3.org/1998/Math/MathML`:o||=`http://www.w3.org/1999/xhtml`,s!=null){for(d=0;d<s.length;d++)if((g=s[d])&&`setAttribute`in g==!!C&&(C?g.localName==C:g.nodeType==3)){n=g,s[d]=null;break}}if(n==null){if(C==null)return document.createTextNode(S);n=document.createElementNS(o,C,S.is&&S),l&&=(t.__m&&t.__m(r,s),!1),s=null}if(C==null)x===S||l&&n.data==S||(n.data=S);else{if(s&&=e.call(n.childNodes),!l&&s!=null)for(x={},d=0;d<n.attributes.length;d++)x[(g=n.attributes[d]).name]=g.value;for(d in x)g=x[d],d==`dangerouslySetInnerHTML`?p=g:d==`children`||d in S||d==`value`&&`defaultValue`in S||d==`checked`&&`defaultChecked`in S||N(n,d,null,g,o);for(d in S)g=S[d],d==`children`?h=g:d==`dangerouslySetInnerHTML`?f=g:d==`value`?v=g:d==`checked`?b=g:l&&typeof g!=`function`||x[d]===g||N(n,d,g,x[d],o);if(f)l||p&&(f.__html==p.__html||f.__html==n.innerHTML)||(n.innerHTML=f.__html),r.__k=[];else if(p&&(n.innerHTML=``),ee(r.type==`template`?n.content:n,_(h)?h:[h],r,i,a,C==`foreignObject`?`http://www.w3.org/1999/xhtml`:o,s,c,s?s[0]:i.__k&&w(i,0),l,u),s!=null)for(d=s.length;d--;)y(s[d]);l||(d=`value`,C==`progress`&&v==null?n.removeAttribute(`value`):v!=null&&(v!==n[d]||C==`progress`&&!v||C==`option`&&v!=x[d])&&N(n,d,v,x[d],o),d=`checked`,b!=null&&b!=n[d]&&N(n,d,b,x[d],o))}return n}function z(e,n,r){try{if(typeof e==`function`){var i=typeof e.__u==`function`;i&&e.__u(),i&&n==null||(e.__u=e(n))}else e.current=n}catch(e){t.__e(e,r)}}function B(e,n,r){var i,a;if(t.unmount&&t.unmount(e),(i=e.ref)&&(i.current&&i.current!=e.__e||z(i,null,n)),(i=e.__c)!=null){if(i.componentWillUnmount)try{i.componentWillUnmount()}catch(e){t.__e(e,n)}i.base=i.__P=null}if(i=e.__k)for(a=0;a<i.length;a++)i[a]&&B(i[a],n,r||typeof e.type!=`function`);r||y(e.__e),e.__c=e.__=e.__e=void 0}function ne(e,t,n){return this.constructor(e,n)}function re(n,r,i){var a,o,s,c;r==document&&(r=document.documentElement),t.__&&t.__(n,r),o=(a=typeof i==`function`)?null:i&&i.__k||r.__k,s=[],c=[],F(r,n=(!a&&i||r).__k=b(S,null,[n]),o||m,m,r.namespaceURI,!a&&i?[i]:o?null:r.firstChild?e.call(r.childNodes):null,s,!a&&i?i:o?o.__e:r.firstChild,a,c),L(s,n,c)}e=h.slice,t={__e:function(e,t,n,r){for(var i,a,o;t=t.__;)if((i=t.__c)&&!i.__)try{if((a=i.constructor)&&a.getDerivedStateFromError!=null&&(i.setState(a.getDerivedStateFromError(e)),o=i.__d),i.componentDidCatch!=null&&(i.componentDidCatch(e,r||{}),o=i.__d),o)return i.__E=i}catch(t){e=t}throw e}},n=0,C.prototype.setState=function(e,t){var n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=v({},this.state);typeof e==`function`&&(e=e(v({},n),this.props)),e&&v(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),D(this))},C.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),D(this))},C.prototype.render=S,r=[],a=typeof Promise==`function`?Promise.prototype.then.bind(Promise.resolve()):setTimeout,o=function(e,t){return e.__v.__b-t.__v.__b},O.__r=0,s=Math.random().toString(8),c=`__d`+s,l=`__a`+s,u=/(PointerCapture)$|Capture$/i,d=0,f=P(!1),p=P(!0);var V,H,U,W,G=0,K=[],q=t,J=q.__b,Y=q.__r,X=q.diffed,ie=q.__c,ae=q.unmount,oe=q.__;function se(e,t){q.__h&&q.__h(H,e,G||t),G=0;var n=H.__H||={__:[],__h:[]};return e>=n.__.length&&n.__.push({}),n.__[e]}function ce(e){return G=1,le(pe,e)}function le(e,t,n){var r=se(V++,2);if(r.t=e,!r.__c&&(r.__=[n?n(t):pe(void 0,t),function(e){var t=r.__N?r.__N[0]:r.__[0],n=r.t(t,e);t!==n&&(r.__N=[n,r.__[1]],r.__c.setState({}))}],r.__c=H,!H.__f)){var i=function(e,t,n){if(!r.__c.__H)return!0;var i=r.__c.__H.__.filter(function(e){return e.__c});if(i.every(function(e){return!e.__N}))return!a||a.call(this,e,t,n);var o=r.__c.props!==e;return i.some(function(e){if(e.__N){var t=e.__[0];e.__=e.__N,e.__N=void 0,t!==e.__[0]&&(o=!0)}}),a&&a.call(this,e,t,n)||o};H.__f=!0;var a=H.shouldComponentUpdate,o=H.componentWillUpdate;H.componentWillUpdate=function(e,t,n){if(this.__e){var r=a;a=void 0,i(e,t,n),a=r}o&&o.call(this,e,t,n)},H.shouldComponentUpdate=i}return r.__N||r.__}function ue(){for(var e;e=K.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(Z),t.__h.some(Q),t.__h=[]}catch(n){t.__h=[],q.__e(n,e.__v)}}}q.__b=function(e){H=null,J&&J(e)},q.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),oe&&oe(e,t)},q.__r=function(e){Y&&Y(e),V=0;var t=(H=e.__c).__H;t&&(U===H?(t.__h=[],H.__h=[],t.__.some(function(e){e.__N&&(e.__=e.__N),e.u=e.__N=void 0})):(t.__h.some(Z),t.__h.some(Q),t.__h=[],V=0)),U=H},q.diffed=function(e){X&&X(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(K.push(t)!==1&&W===q.requestAnimationFrame||((W=q.requestAnimationFrame)||fe)(ue)),t.__H.__.some(function(e){e.u&&(e.__H=e.u),e.u=void 0})),U=H=null},q.__c=function(e,t){t.some(function(e){try{e.__h.some(Z),e.__h=e.__h.filter(function(e){return!e.__||Q(e)})}catch(n){t.some(function(e){e.__h&&=[]}),t=[],q.__e(n,e.__v)}}),ie&&ie(e,t)},q.unmount=function(e){ae&&ae(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.some(function(e){try{Z(e)}catch(e){t=e}}),n.__H=void 0,t&&q.__e(t,n.__v))};var de=typeof requestAnimationFrame==`function`;function fe(e){var t,n=function(){clearTimeout(r),de&&cancelAnimationFrame(t),setTimeout(e)},r=setTimeout(n,35);de&&(t=requestAnimationFrame(n))}function Z(e){var t=H,n=e.__c;typeof n==`function`&&(e.__c=void 0,n()),H=t}function Q(e){var t=H;e.__c=e.__(),H=t}function pe(e,t){return typeof t==`function`?t(e):t}var me=0;Array.isArray;function $(e,n,r,i,a,o){n||={};var s,c,l=n;if(`ref`in l)for(c in l={},n)c==`ref`?s=n[c]:l[c]=n[c];var u={type:e,props:l,key:r,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--me,__i:-1,__u:0,__source:a,__self:o};if(typeof e==`function`&&(s=e.defaultProps))for(c in s)l[c]===void 0&&(l[c]=s[c]);return t.vnode&&t.vnode(u),u}function he(){let[e,t]=ce(0);return $(S,{children:[$(`button`,{onClick:()=>t(e+1),children:`click me please`}),$(`h1`,{children:e})]})}re($(he,{}),document.getElementById(`app`)); tvwuan8dqr1vv33cpabpgjqj4dnb2yz 739795 739793 2026-04-29T20:19:33Z Enbi 72574 739795 javascript text/javascript (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e,t,n,r,i,a,o,s,c,l,u,d,f,p,m={},h=[],g=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,_=Array.isArray;function v(e,t){for(var n in t)e[n]=t[n];return e}function y(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function b(t,n,r){var i,a,o,s={};for(o in n)o==`key`?i=n[o]:o==`ref`?a=n[o]:s[o]=n[o];if(arguments.length>2&&(s.children=arguments.length>3?e.call(arguments,2):r),typeof t==`function`&&t.defaultProps!=null)for(o in t.defaultProps)s[o]===void 0&&(s[o]=t.defaultProps[o]);return x(t,s,i,a,null)}function x(e,r,i,a,o){var s={type:e,props:r,key:i,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o??++n,__i:-1,__u:0};return o==null&&t.vnode!=null&&t.vnode(s),s}function S(e){return e.children}function C(e,t){this.props=e,this.context=t}function w(e,t){if(t==null)return e.__?w(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type==`function`?w(e):null}function T(e){if(e.__P&&e.__d){var n=e.__v,r=n.__e,i=[],a=[],o=v({},n);o.__v=n.__v+1,t.vnode&&t.vnode(o),F(e.__P,o,n,e.__n,e.__P.namespaceURI,32&n.__u?[r]:null,i,r??w(n),!!(32&n.__u),a),o.__v=n.__v,o.__.__k[o.__i]=o,L(i,o,a),n.__e=n.__=null,o.__e!=r&&E(o)}}function E(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),E(e)}function D(e){(!e.__d&&(e.__d=!0)&&r.push(e)&&!O.__r++||i!=t.debounceRendering)&&((i=t.debounceRendering)||a)(O)}function O(){try{for(var e,t=1;r.length;)r.length>t&&r.sort(o),e=r.shift(),t=r.length,T(e)}finally{r.length=O.__r=0}}function ee(e,t,n,r,i,a,o,s,c,l,u){var d,f,p,g,_,v,y,b=r&&r.__k||h,x=t.length;for(c=k(n,t,b,c,x),d=0;d<x;d++)(p=n.__k[d])!=null&&(f=p.__i!=-1&&b[p.__i]||m,p.__i=d,v=F(e,p,f,i,a,o,s,c,l,u),g=p.__e,p.ref&&f.ref!=p.ref&&(f.ref&&z(f.ref,null,p),u.push(p.ref,p.__c||g,p)),_==null&&g!=null&&(_=g),(y=!!(4&p.__u))||f.__k===p.__k?(c=A(p,c,e,y),y&&f.__e&&(f.__e=null)):typeof p.type==`function`&&v!==void 0?c=v:g&&(c=g.nextSibling),p.__u&=-7);return n.__e=_,c}function k(e,t,n,r,i){var a,o,s,c,l,u=n.length,d=u,f=0;for(e.__k=Array(i),a=0;a<i;a++)(o=t[a])!=null&&typeof o!=`boolean`&&typeof o!=`function`?(typeof o==`string`||typeof o==`number`||typeof o==`bigint`||o.constructor==String?o=e.__k[a]=x(null,o,null,null,null):_(o)?o=e.__k[a]=x(S,{children:o},null,null,null):o.constructor===void 0&&o.__b>0?o=e.__k[a]=x(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):e.__k[a]=o,c=a+f,o.__=e,o.__b=e.__b+1,s=null,(l=o.__i=j(o,n,c,d))!=-1&&(d--,(s=n[l])&&(s.__u|=2)),s==null||s.__v==null?(l==-1&&(i>u?f--:i<u&&f++),typeof o.type!=`function`&&(o.__u|=4)):l!=c&&(l==c-1?f--:l==c+1?f++:(l>c?f--:f++,o.__u|=4))):e.__k[a]=null;if(d)for(a=0;a<u;a++)(s=n[a])!=null&&!(2&s.__u)&&(s.__e==r&&(r=w(s)),B(s,s));return r}function A(e,t,n,r){var i,a;if(typeof e.type==`function`){for(i=e.__k,a=0;i&&a<i.length;a++)i[a]&&(i[a].__=e,t=A(i[a],t,n,r));return t}e.__e!=t&&(r&&(t&&e.type&&!t.parentNode&&(t=w(e)),n.insertBefore(e.__e,t||null)),t=e.__e);do t&&=t.nextSibling;while(t!=null&&t.nodeType==8);return t}function j(e,t,n,r){var i,a,o,s=e.key,c=e.type,l=t[n],u=l!=null&&(2&l.__u)==0;if(l===null&&s==null||u&&s==l.key&&c==l.type)return n;if(r>+!!u){for(i=n-1,a=n+1;i>=0||a<t.length;)if((l=t[o=i>=0?i--:a++])!=null&&!(2&l.__u)&&s==l.key&&c==l.type)return o}return-1}function M(e,t,n){t[0]==`-`?e.setProperty(t,n??``):e[t]=n==null?``:typeof n!=`number`||g.test(t)?n:n+`px`}function N(e,t,n,r,i){var a,o;n:if(t==`style`)if(typeof n==`string`)e.style.cssText=n;else{if(typeof r==`string`&&(e.style.cssText=r=``),r)for(t in r)n&&t in n||M(e.style,t,``);if(n)for(t in n)r&&n[t]==r[t]||M(e.style,t,n[t])}else if(t[0]==`o`&&t[1]==`n`)a=t!=(t=t.replace(u,`$1`)),o=t.toLowerCase(),t=o in e||t==`onFocusOut`||t==`onFocusIn`?o.slice(2):t.slice(2),e.l||={},e.l[t+a]=n,n?r?n[l]=r[l]:(n[l]=d,e.addEventListener(t,a?p:f,a)):e.removeEventListener(t,a?p:f,a);else{if(i==`http://www.w3.org/2000/svg`)t=t.replace(/xlink(H|:h)/,`h`).replace(/sName$/,`s`);else if(t!=`width`&&t!=`height`&&t!=`href`&&t!=`list`&&t!=`form`&&t!=`tabIndex`&&t!=`download`&&t!=`rowSpan`&&t!=`colSpan`&&t!=`role`&&t!=`popover`&&t in e)try{e[t]=n??``;break n}catch{}typeof n==`function`||(n==null||!1===n&&t[4]!=`-`?e.removeAttribute(t):e.setAttribute(t,t==`popover`&&n==1?``:n))}}function P(e){return function(n){if(this.l){var r=this.l[n.type+e];if(n[c]==null)n[c]=d++;else if(n[c]<r[l])return;return r(t.event?t.event(n):n)}}}function F(e,n,r,i,a,o,s,c,l,u){var d,f,p,m,g,b,x,w,T,E,D,O,k,A,j,M=n.type;if(n.constructor!==void 0)return null;128&r.__u&&(l=!!(32&r.__u),o=[c=n.__e=r.__e]),(d=t.__b)&&d(n);n:if(typeof M==`function`)try{if(w=n.props,T=M.prototype&&M.prototype.render,E=(d=M.contextType)&&i[d.__c],D=d?E?E.props.value:d.__:i,r.__c?x=(f=n.__c=r.__c).__=f.__E:(T?n.__c=f=new M(w,D):(n.__c=f=new C(w,D),f.constructor=M,f.render=ne),E&&E.sub(f),f.state||={},f.__n=i,p=f.__d=!0,f.__h=[],f._sb=[]),T&&f.__s==null&&(f.__s=f.state),T&&M.getDerivedStateFromProps!=null&&(f.__s==f.state&&(f.__s=v({},f.__s)),v(f.__s,M.getDerivedStateFromProps(w,f.__s))),m=f.props,g=f.state,f.__v=n,p)T&&M.getDerivedStateFromProps==null&&f.componentWillMount!=null&&f.componentWillMount(),T&&f.componentDidMount!=null&&f.__h.push(f.componentDidMount);else{if(T&&M.getDerivedStateFromProps==null&&w!==m&&f.componentWillReceiveProps!=null&&f.componentWillReceiveProps(w,D),n.__v==r.__v||!f.__e&&f.shouldComponentUpdate!=null&&!1===f.shouldComponentUpdate(w,f.__s,D)){n.__v!=r.__v&&(f.props=w,f.state=f.__s,f.__d=!1),n.__e=r.__e,n.__k=r.__k,n.__k.some(function(e){e&&(e.__=n)}),h.push.apply(f.__h,f._sb),f._sb=[],f.__h.length&&s.push(f);break n}f.componentWillUpdate!=null&&f.componentWillUpdate(w,f.__s,D),T&&f.componentDidUpdate!=null&&f.__h.push(function(){f.componentDidUpdate(m,g,b)})}if(f.context=D,f.props=w,f.__P=e,f.__e=!1,O=t.__r,k=0,T)f.state=f.__s,f.__d=!1,O&&O(n),d=f.render(f.props,f.state,f.context),h.push.apply(f.__h,f._sb),f._sb=[];else do f.__d=!1,O&&O(n),d=f.render(f.props,f.state,f.context),f.state=f.__s;while(f.__d&&++k<25);f.state=f.__s,f.getChildContext!=null&&(i=v(v({},i),f.getChildContext())),T&&!p&&f.getSnapshotBeforeUpdate!=null&&(b=f.getSnapshotBeforeUpdate(m,g)),A=d!=null&&d.type===S&&d.key==null?R(d.props.children):d,c=ee(e,_(A)?A:[A],n,r,i,a,o,s,c,l,u),f.base=n.__e,n.__u&=-161,f.__h.length&&s.push(f),x&&(f.__E=f.__=null)}catch(e){if(n.__v=null,l||o!=null)if(e.then){for(n.__u|=l?160:128;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;o[o.indexOf(c)]=null,n.__e=c}else{for(j=o.length;j--;)y(o[j]);I(n)}else n.__e=r.__e,n.__k=r.__k,e.then||I(n);t.__e(e,n,r)}else o==null&&n.__v==r.__v?(n.__k=r.__k,n.__e=r.__e):c=n.__e=te(r.__e,n,r,i,a,o,s,l,u);return(d=t.diffed)&&d(n),128&n.__u?void 0:c}function I(e){e&&(e.__c&&(e.__c.__e=!0),e.__k&&e.__k.some(I))}function L(e,n,r){for(var i=0;i<r.length;i++)z(r[i],r[++i],r[++i]);t.__c&&t.__c(n,e),e.some(function(n){try{e=n.__h,n.__h=[],e.some(function(e){e.call(n)})}catch(e){t.__e(e,n.__v)}})}function R(e){return typeof e!=`object`||!e||e.__b>0?e:_(e)?e.map(R):v({},e)}function te(n,r,i,a,o,s,c,l,u){var d,f,p,h,g,v,b,x=i.props||m,S=r.props,C=r.type;if(C==`svg`?o=`http://www.w3.org/2000/svg`:C==`math`?o=`http://www.w3.org/1998/Math/MathML`:o||=`http://www.w3.org/1999/xhtml`,s!=null){for(d=0;d<s.length;d++)if((g=s[d])&&`setAttribute`in g==!!C&&(C?g.localName==C:g.nodeType==3)){n=g,s[d]=null;break}}if(n==null){if(C==null)return document.createTextNode(S);n=document.createElementNS(o,C,S.is&&S),l&&=(t.__m&&t.__m(r,s),!1),s=null}if(C==null)x===S||l&&n.data==S||(n.data=S);else{if(s&&=e.call(n.childNodes),!l&&s!=null)for(x={},d=0;d<n.attributes.length;d++)x[(g=n.attributes[d]).name]=g.value;for(d in x)g=x[d],d==`dangerouslySetInnerHTML`?p=g:d==`children`||d in S||d==`value`&&`defaultValue`in S||d==`checked`&&`defaultChecked`in S||N(n,d,null,g,o);for(d in S)g=S[d],d==`children`?h=g:d==`dangerouslySetInnerHTML`?f=g:d==`value`?v=g:d==`checked`?b=g:l&&typeof g!=`function`||x[d]===g||N(n,d,g,x[d],o);if(f)l||p&&(f.__html==p.__html||f.__html==n.innerHTML)||(n.innerHTML=f.__html),r.__k=[];else if(p&&(n.innerHTML=``),ee(r.type==`template`?n.content:n,_(h)?h:[h],r,i,a,C==`foreignObject`?`http://www.w3.org/1999/xhtml`:o,s,c,s?s[0]:i.__k&&w(i,0),l,u),s!=null)for(d=s.length;d--;)y(s[d]);l||(d=`value`,C==`progress`&&v==null?n.removeAttribute(`value`):v!=null&&(v!==n[d]||C==`progress`&&!v||C==`option`&&v!=x[d])&&N(n,d,v,x[d],o),d=`checked`,b!=null&&b!=n[d]&&N(n,d,b,x[d],o))}return n}function z(e,n,r){try{if(typeof e==`function`){var i=typeof e.__u==`function`;i&&e.__u(),i&&n==null||(e.__u=e(n))}else e.current=n}catch(e){t.__e(e,r)}}function B(e,n,r){var i,a;if(t.unmount&&t.unmount(e),(i=e.ref)&&(i.current&&i.current!=e.__e||z(i,null,n)),(i=e.__c)!=null){if(i.componentWillUnmount)try{i.componentWillUnmount()}catch(e){t.__e(e,n)}i.base=i.__P=null}if(i=e.__k)for(a=0;a<i.length;a++)i[a]&&B(i[a],n,r||typeof e.type!=`function`);r||y(e.__e),e.__c=e.__=e.__e=void 0}function ne(e,t,n){return this.constructor(e,n)}function re(n,r,i){var a,o,s,c;r==document&&(r=document.documentElement),t.__&&t.__(n,r),o=(a=typeof i==`function`)?null:i&&i.__k||r.__k,s=[],c=[],F(r,n=(!a&&i||r).__k=b(S,null,[n]),o||m,m,r.namespaceURI,!a&&i?[i]:o?null:r.firstChild?e.call(r.childNodes):null,s,!a&&i?i:o?o.__e:r.firstChild,a,c),L(s,n,c)}e=h.slice,t={__e:function(e,t,n,r){for(var i,a,o;t=t.__;)if((i=t.__c)&&!i.__)try{if((a=i.constructor)&&a.getDerivedStateFromError!=null&&(i.setState(a.getDerivedStateFromError(e)),o=i.__d),i.componentDidCatch!=null&&(i.componentDidCatch(e,r||{}),o=i.__d),o)return i.__E=i}catch(t){e=t}throw e}},n=0,C.prototype.setState=function(e,t){var n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=v({},this.state);typeof e==`function`&&(e=e(v({},n),this.props)),e&&v(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),D(this))},C.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),D(this))},C.prototype.render=S,r=[],a=typeof Promise==`function`?Promise.prototype.then.bind(Promise.resolve()):setTimeout,o=function(e,t){return e.__v.__b-t.__v.__b},O.__r=0,s=Math.random().toString(8),c=`__d`+s,l=`__a`+s,u=/(PointerCapture)$|Capture$/i,d=0,f=P(!1),p=P(!0);var V,H,U,W,G=0,K=[],q=t,J=q.__b,Y=q.__r,X=q.diffed,ie=q.__c,ae=q.unmount,oe=q.__;function se(e,t){q.__h&&q.__h(H,e,G||t),G=0;var n=H.__H||={__:[],__h:[]};return e>=n.__.length&&n.__.push({}),n.__[e]}function ce(e){return G=1,le(pe,e)}function le(e,t,n){var r=se(V++,2);if(r.t=e,!r.__c&&(r.__=[n?n(t):pe(void 0,t),function(e){var t=r.__N?r.__N[0]:r.__[0],n=r.t(t,e);t!==n&&(r.__N=[n,r.__[1]],r.__c.setState({}))}],r.__c=H,!H.__f)){var i=function(e,t,n){if(!r.__c.__H)return!0;var i=r.__c.__H.__.filter(function(e){return e.__c});if(i.every(function(e){return!e.__N}))return!a||a.call(this,e,t,n);var o=r.__c.props!==e;return i.some(function(e){if(e.__N){var t=e.__[0];e.__=e.__N,e.__N=void 0,t!==e.__[0]&&(o=!0)}}),a&&a.call(this,e,t,n)||o};H.__f=!0;var a=H.shouldComponentUpdate,o=H.componentWillUpdate;H.componentWillUpdate=function(e,t,n){if(this.__e){var r=a;a=void 0,i(e,t,n),a=r}o&&o.call(this,e,t,n)},H.shouldComponentUpdate=i}return r.__N||r.__}function ue(){for(var e;e=K.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(Z),t.__h.some(Q),t.__h=[]}catch(n){t.__h=[],q.__e(n,e.__v)}}}q.__b=function(e){H=null,J&&J(e)},q.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),oe&&oe(e,t)},q.__r=function(e){Y&&Y(e),V=0;var t=(H=e.__c).__H;t&&(U===H?(t.__h=[],H.__h=[],t.__.some(function(e){e.__N&&(e.__=e.__N),e.u=e.__N=void 0})):(t.__h.some(Z),t.__h.some(Q),t.__h=[],V=0)),U=H},q.diffed=function(e){X&&X(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(K.push(t)!==1&&W===q.requestAnimationFrame||((W=q.requestAnimationFrame)||fe)(ue)),t.__H.__.some(function(e){e.u&&(e.__H=e.u),e.u=void 0})),U=H=null},q.__c=function(e,t){t.some(function(e){try{e.__h.some(Z),e.__h=e.__h.filter(function(e){return!e.__||Q(e)})}catch(n){t.some(function(e){e.__h&&=[]}),t=[],q.__e(n,e.__v)}}),ie&&ie(e,t)},q.unmount=function(e){ae&&ae(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.some(function(e){try{Z(e)}catch(e){t=e}}),n.__H=void 0,t&&q.__e(t,n.__v))};var de=typeof requestAnimationFrame==`function`;function fe(e){var t,n=function(){clearTimeout(r),de&&cancelAnimationFrame(t),setTimeout(e)},r=setTimeout(n,35);de&&(t=requestAnimationFrame(n))}function Z(e){var t=H,n=e.__c;typeof n==`function`&&(e.__c=void 0,n()),H=t}function Q(e){var t=H;e.__c=e.__(),H=t}function pe(e,t){return typeof t==`function`?t(e):t}var me=0;Array.isArray;function $(e,n,r,i,a,o){n||={};var s,c,l=n;if(`ref`in l)for(c in l={},n)c==`ref`?s=n[c]:l[c]=n[c];var u={type:e,props:l,key:r,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--me,__i:-1,__u:0,__source:a,__self:o};if(typeof e==`function`&&(s=e.defaultProps))for(c in s)l[c]===void 0&&(l[c]=s[c]);return t.vnode&&t.vnode(u),u}function he(){let[e,t]=ce(0);return console.log(`the APP has LOADED`),$(S,{children:[$(`button`,{onClick:()=>t(e+1),children:`click me please`}),$(`h1`,{children:e})]})}re($(he,{}),document.getElementById(`app`)); 75fcfdn4kz2lh9zon304znxcwqv85d1 739796 739795 2026-04-29T20:20:12Z Enbi 72574 Blanked the page 739796 javascript text/javascript phoiac9h4m842xq45sp7s6u21eteeq1 User:TheAuroraBorealis/moderncontribs.js 2 175057 739799 2026-04-29T21:28:15Z TheAuroraBorealis 73590 Created page with "if (mw.config.get("skin") === "modern"){ if(mw.config.get("wgPageName") === "Contributions" && mw.config.get("wgCanonicalNamespace") === "Special"){ document.querySelector("header").style.display = "none"; } }" 739799 javascript text/javascript if (mw.config.get("skin") === "modern"){ if(mw.config.get("wgPageName") === "Contributions" && mw.config.get("wgCanonicalNamespace") === "Special"){ document.querySelector("header").style.display = "none"; } } n52syu9jel5vawyuad2cixfk7c05hbf 739802 739799 2026-04-29T21:46:39Z TheAuroraBorealis 73590 739802 javascript text/javascript if (mw.config.get("skin") === "modern"){ if(mw.config.get("wgPageName") === "Special:Contributions/" + mw.config.get("mwUserName")){ document.querySelector("header").style.display = "none"; } } dy5m1nikh3y8dnos5o12o6i11o6vn57 739803 739802 2026-04-29T21:47:21Z TheAuroraBorealis 73590 739803 javascript text/javascript if (mw.config.get("skin") === "modern"){ alert("Special:Contributions/" + mw.config.get("mwUserName")); if(mw.config.get("wgPageName") === "Special:Contributions/" + mw.config.get("mwUserName")){ alert("Running!"); document.querySelector("header").style.display = "none"; } } exb4kixdomog3619u2ab54kcofmj2f5 739804 739803 2026-04-29T21:47:41Z TheAuroraBorealis 73590 739804 javascript text/javascript if (mw.config.get("skin") === "modern"){ alert("Special:Contributions/" + mw.config.get("wgUserName")); if(mw.config.get("wgPageName") === "Special:Contributions/" + mw.config.get("wgUserName")){ alert("Running!"); document.querySelector("header").style.display = "none"; } } lvq6vikb9s86mxjbe3on1dimg54d1ap 739805 739804 2026-04-29T21:48:33Z TheAuroraBorealis 73590 739805 javascript text/javascript if (mw.config.get("skin") === "modern"){ if(mw.config.get("wgPageName") === "Special:Contributions/" + mw.config.get("wgUserName")){ document.querySelector("header").style.display = "none"; } } r4wlyzgcfcaigi1ilic9odcvfhvt2tr User:TheAuroraBorealis/common.js 2 175058 739800 2026-04-29T21:42:54Z TheAuroraBorealis 73590 Created page with "mw.loader.load( '//en.wikipedia.org/wiki/User:TheAuroraBorealis/moderncontribs.js?action=raw&ctype=text/javascript' );" 739800 javascript text/javascript mw.loader.load( '//en.wikipedia.org/wiki/User:TheAuroraBorealis/moderncontribs.js?action=raw&ctype=text/javascript' ); kx8qmi7x4o4580xqisgrec8abswg8y9 739801 739800 2026-04-29T21:43:16Z TheAuroraBorealis 73590 739801 javascript text/javascript mw.loader.load( '//test.wikipedia.org/wiki/User:TheAuroraBorealis/moderncontribs.js?action=raw&ctype=text/javascript' ); gtjcm0z1ug7c31tkka7duoczp7cit05 739806 739801 2026-04-29T21:51:16Z TheAuroraBorealis 73590 Blanked the page 739806 javascript text/javascript phoiac9h4m842xq45sp7s6u21eteeq1 Talk:MyTemplate 1 175059 739810 2026-04-30T01:10:17Z Leojqian 71700 /* hello test */ new section 739810 wikitext text/x-wiki == hello test == asdf [[User:Leojqian|Leojqian]] ([[User talk:Leojqian|talk]]) 01:10, 30 April 2026 (UTC) sn7oooyacweiywwy0nt0bq0yp3pflqg User:Nil NZ/common.js 2 175060 739814 2026-04-30T01:15:58Z Nil NZ 70294 Created page with "mw.loader.load('http://localhost:4444'); // afch dev" 739814 javascript text/javascript mw.loader.load('http://localhost:4444'); // afch dev 2fugd9n2iiqpi7tcileujqav8aoauyh 739817 739814 2026-04-30T02:04:10Z Nil NZ 70294 Installing AFCH from localhost ([[MediaWiki:Setup-afch-dev.js]]) 739817 javascript text/javascript mw.loader.load('http://localhost:4444'); // afch dev mw.loader.load('http://localhost:4444?ctype=text/javascript&title=afch-dev.js', 'text/javascript'); 2uiifuyrkfh78fzdtel92rv32o873r5 Draft:Nil 118 175061 739815 2026-04-30T01:44:25Z Nil NZ 70294 Creating test draft 739815 wikitext text/x-wiki Hello world eek1k2omqpxg1kv7w7uexdezwncy0ke 739816 739815 2026-04-30T01:44:49Z Nil NZ 70294 Afc submit 739816 wikitext text/x-wiki {{afc submission}} Hello world px6z3bfe5jf2zporl6opmwr1yj7j6a4 User:Nil NZ/Testing AFCH 2 175062 739818 2026-04-30T02:04:10Z Nil NZ 70294 Creating test draft ([[MediaWiki:Setup-afch-dev.js]]) 739818 wikitext text/x-wiki {{AfC submission|||ts=20211228040845|u=Enterprisey|ns=8}} Test draft! [[User:Nil NZ|Nil NZ]] ([[User talk:Nil NZ|talk]]) 02:04, 30 April 2026 (UTC) ljwoujsvvn64k364kqu7tghopxfuvo9 Grevel 0 175063 739838 2026-04-30T07:45:41Z NWBibBot 72927 Created page with "{{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Grevel | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.565 | Längengrad = 7.550 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 90 <!--www.tim-online.nrw.de--> | Höhe-Bezug..." 739838 wikitext text/x-wiki {{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Grevel | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.565 | Längengrad = 7.550 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 90 <!--www.tim-online.nrw.de--> | Höhe-Bezug = NHN | Fläche = 4.33 | Einwohner = 633 | Einwohner-Stand-Datum = 2013-12-31 | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 44329 | Postleitzahl2 = | Vorwahl1 = 0231 {{!}}- class="hintergrundfarbe1" {{!}} [[Statistischer Bezirk|Unterbezirk]]: {{!}} 221 | Lagekarte = | Lagekarte-Beschreibung = }} '''Grevel''' ([[niederdeutsch]]: Griëwel<ref>Wilhelm Schleef: ''Dortmunder Wörterbuch.'' 1967. XXII, 298 S. Ln. ([https://www.lwl.org/komuna/pdf/Bd_15.pdf lwl.org] PDF; 3,9 MB).</ref>) ist ein dorfähnlicher Stadtteil im [[Dortmund]]er Nordosten und gehört zum [[Stadtbezirk Scharnhorst]]. == Geschichte == Grevel wurde erstmals im Jahre 1193 mit ''curtes Marsuelde et Greuele et Wande'' urkundlich erwähnt. In den Jahren 1197 bis 1305 wird der Ort urkundlich ''Griuele, Greuilo, Greuele'' oder ''Grevele'' genannt. 1281 bis 1313 wurde ein ''Theodericus [[Ritter|miles]] dictus Vridach curtim in Grevele'' mit dem Ort [[Lehnswesen|belehnt]] sowie weitere Mitglieder der [[Adel]]sfamilie [[Frydag (Adelsgeschlecht)|von Frydag]] mit Grevel von 1317 bis 1400 belehnt. 1380 wird ein ''Thilemannus de Grevele'' im Dortmunder Urkundenbuch genannt. Im Jahr 1398 ein ''Johannes Grivel'' im [[Volmestein|Volmarsteiner]] Lehnsregister geführt. Grevel gehörte im [[Spätmittelalter]] und der [[Frühe Neuzeit|Frühen Neuzeit]] in eigener [[Bauerschaft]] (''Grevelkuylre'') im [[Kirchspiel]] [[Kurl]] und Amt Unna (historisch) zur [[Grafschaft Mark]]. Laut dem [[Schatzbuch der Grafschaft Mark]] von 1486 hatten in der Bauerschaft 22 Steuerpflichtige Hofbesitzer zwischen 1 oirt und 6 [[Rheinischer Gulden|Goldgulden]] an Abgabe zu leisten.<ref>[[Aloys Meister]]: ''Die Grafschaft Mark'', Festschrift zum Gedächtnis der 300-jährigen Vereinigung mit Brandenburg-Preußen. 2. Band, Dortmund 1909, S.&nbsp;22 – Auszug aus dem Schatzbuch der Grafschaft Mark von 1486 (Bauerschaft Grevel/Kurl)</ref> Im Jahr 1705 waren in der ''Baurschafft Grevel'' 18 Steuerpflichtige mit Abgaben an die Rentei Unna im [[Kataster]] verzeichnet.<ref>''Westfälisches Schatzungs- und Steuerregister'', Band 6, Münster 1980. Darin: ''Kataster der Kontribuablen Güter in der Grafschaft Mark 1705'', Bearb. von Willy Timm, S.&nbsp;75</ref> Die Deutung des Ortsnamens bleibt unklar. Es ist mit einer Motivation durch eine Gegebenheit des Geländes, etwa einen Graben oder eine Grube, zu rechnen, die möglicherweise nicht mehr existiert.<ref>Michael Flöer: ''Die Ortsnamen der Stadt Dortmund und der Stadt Hagen'', in: Westfälisches Ortsnamenbuch, Band 16, Bielefeld 2021, S.&nbsp;105–108</ref> Im 19. Jahrhundert war Grevel eine Landgemeinde im [[Landkreis Dortmund]] und [[Amt Brackel]]. 1885 hatte die Gemeinde (plus 4 [[Wohnplatz|Wohnplätze]]) eine Fläche von 4,33 km², davon 241 [[Hektar|ha]] Ackerland, 19 ha Wiesen und 76 ha Holzungen. Es gab 53 Wohngebäude mit 67 Haushaltungen und 378 Einwohner.<ref>Gemeindelexikon für die Provinz Westfalen. Berlin 1887, S.&nbsp;80/81, Online-Ausgabe Münster, Universitäts- und Landesbibliothek 2014</ref> Grevel wurde am 1. April 1928 nach Dortmund eingemeindet.<ref>{{BibISBN|3402058758|Seite=237}}</ref> Der heutige Stadtteil Grevel ist dünn besiedelt und besitzt mit seiner Vielzahl an Bauernhöfen einen ländlichen Charakter. Zu den Sehenswürdigkeiten des kleinen Ortes zählen der [[Wasserturm]] ''[[Lanstroper Ei]]'' und der [[Luftschacht Rote Fuhr]] im Rahmen der [[Route der Industriekultur]] sowie die [[Alte Mühle Grevel|Alte Mühle]]. Zu Grevel gehört auch der Hienberg, der sich an die [[Deponie]] Nord-Ost anschließt. Die südlich anschließende Großsiedlung [[Scharnhorst-Ost]] entstand ab 1965 auf einem Areal, das bei Baubeginn noch zum Stadtteil Grevel gehörte, dann aber dem 1931 gebildeten Stadtteil Scharnhorst ([[Alt-Scharnhorst]]) zugeschlagen wurde. Grevel verlor durch die Großsiedlung zwar fast die Hälfte seiner Fläche, behielt durch die Abtretung jedoch seine ländliche Prägung. Im Gegensatz zu den meisten Dortmunder Stadtteilen stehen in Grevel noch eine hohe Anzahl an sehenswerten Fachwerkhäusern, und der Ort hat besonders an der ''Greveler Straße'' und dem ''Werzenkamp'' seinen dörflichen Charakter erhalten. == Bevölkerung == {{PanoViewer|Grevel Panorama 360.jpg|Grevel 360° Panorama}} Sozialstruktur der Greveler Bevölkerung: * Bevölkerungsdichte: 76 Einwohner pro [[Hektar]] Siedlungsfläche. * Minderjährigenquote: 17,7 %, leicht unterhalb des Dortmunder Durchschnittes. * Altenquote: 31,9 %, leicht oberhalb des Dortmunder Durchschnittes. * Ausländeranteil: 1,1 %, einer der niedrigsten Dortmunder Werte. * Arbeitslosenquote: 7,4 %, deutlich unterhalb des Dortmunder Durchschnittes. Das durchschnittliche Einkommen liegt etwa 20 % oberhalb des Dortmunder Durchschnittes. {| class="wikitable" style="text-align:center;" |+ Bevölkerungsentwicklung |- ! style="text-align:left;"| '''Jahr'''|| 2003|| 2008|| 2009|| 2013 |- |style="text-align:left;"| '''Einwohner'''|| 632|| 633|| 632|| 633<ref>{{Internetquelle|url=https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|titel=Statistikatlas 2015|hrsg=Stadt Dortmund – Stabsstelle Dortmunder Statistik|seiten=15|datum=2015-07|format=PDF; 24,2&nbsp;MB|offline=1|archiv-url=https://web.archive.org/web/20160914110041/https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|archiv-datum=2016-09-14|archiv-bot=2019-09-08 08:17:30 InternetArchiveBot|abruf=2016-06-29}}</ref> |} <gallery> Hienberg.jpg|Der Hienberg von [[Lanstrop]] aus gesehen Lei5.jpg|Der Wasserturm ''[[Lanstroper Ei]]'' BlickDerne.jpg|Blick vom Hienberg auf [[Derne]] und die [[Zeche Gneisenau]] Dortmund-Scharnhorst, Luftschacht Rote Fuhr, Totale.jpg|Der Luftschacht ''Rote Fuhr'' DSW405Grevel.jpg|Stadtbahn an der Endstation Grevel </gallery> == Verkehr == Grevel ist der nördliche Endpunkt der Linie U42 der [[Stadtbahn Dortmund]]. Sie verbindet Grevel alle zehn Minuten mit [[Scharnhorst-Ost|Scharnhorst]], [[Kirchderne]], [[Eving]], [[City (Dortmund)|Innenstadt]] und den südlichen Stadtteilen [[Barop]] und [[Hombruch]]. Am U-Bahnhof Grevel besteht ein Übergang zur Buslinie 423 nach [[Lanstrop]]. Es ist auch geplant, die U42 nach Lanstrop zu verlängern, um diesen Ort besser an die Innenstadt anzubinden. Dies soll aber erst langfristig umgesetzt werden. {| class="wikitable" |- class="hintergrundfarbe6" ! Linie ! Verlauf ! Takt {{Linienverlauf SPNV Rhein-Ruhr|U42}} |} Über die Anschlussstelle „Dortmund-Lanstrop“ ist Grevel an die [[Bundesautobahn 2]] und damit an das Fernstraßennetz angeschlossen, allerdings nur in Richtung Hannover. == Einzelnachweise == <references /> {{Navigationsleiste Stadtteile von Dortmund}} {{Normdaten|TYP=g|GND=4402970-6|VIAF=236097878}} [[Kategorie:Unterbezirk von Dortmund]] [[Kategorie:Ehemalige Gemeinde (Dortmund)]] [[Kategorie:Gemeindeauflösung 1928]] [[Kategorie:Ersterwähnung 1193]] qdiumcdvq293ef8xnjf9qg26xqo2jbq 739839 739838 2026-04-30T07:50:53Z NWBibBot 72927 Bot: Ergänze Link zur NWBib mit NWBib Template 739839 wikitext text/x-wiki {{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Grevel | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.565 | Längengrad = 7.550 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 90 <!--www.tim-online.nrw.de--> | Höhe-Bezug = NHN | Fläche = 4.33 | Einwohner = 633 | Einwohner-Stand-Datum = 2013-12-31 | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 44329 | Postleitzahl2 = | Vorwahl1 = 0231 {{!}}- class="hintergrundfarbe1" {{!}} [[Statistischer Bezirk|Unterbezirk]]: {{!}} 221 | Lagekarte = | Lagekarte-Beschreibung = }} '''Grevel''' ([[niederdeutsch]]: Griëwel<ref>Wilhelm Schleef: ''Dortmunder Wörterbuch.'' 1967. XXII, 298 S. Ln. ([https://www.lwl.org/komuna/pdf/Bd_15.pdf lwl.org] PDF; 3,9 MB).</ref>) ist ein dorfähnlicher Stadtteil im [[Dortmund]]er Nordosten und gehört zum [[Stadtbezirk Scharnhorst]]. == Geschichte == Grevel wurde erstmals im Jahre 1193 mit ''curtes Marsuelde et Greuele et Wande'' urkundlich erwähnt. In den Jahren 1197 bis 1305 wird der Ort urkundlich ''Griuele, Greuilo, Greuele'' oder ''Grevele'' genannt. 1281 bis 1313 wurde ein ''Theodericus [[Ritter|miles]] dictus Vridach curtim in Grevele'' mit dem Ort [[Lehnswesen|belehnt]] sowie weitere Mitglieder der [[Adel]]sfamilie [[Frydag (Adelsgeschlecht)|von Frydag]] mit Grevel von 1317 bis 1400 belehnt. 1380 wird ein ''Thilemannus de Grevele'' im Dortmunder Urkundenbuch genannt. Im Jahr 1398 ein ''Johannes Grivel'' im [[Volmestein|Volmarsteiner]] Lehnsregister geführt. Grevel gehörte im [[Spätmittelalter]] und der [[Frühe Neuzeit|Frühen Neuzeit]] in eigener [[Bauerschaft]] (''Grevelkuylre'') im [[Kirchspiel]] [[Kurl]] und Amt Unna (historisch) zur [[Grafschaft Mark]]. Laut dem [[Schatzbuch der Grafschaft Mark]] von 1486 hatten in der Bauerschaft 22 Steuerpflichtige Hofbesitzer zwischen 1 oirt und 6 [[Rheinischer Gulden|Goldgulden]] an Abgabe zu leisten.<ref>[[Aloys Meister]]: ''Die Grafschaft Mark'', Festschrift zum Gedächtnis der 300-jährigen Vereinigung mit Brandenburg-Preußen. 2. Band, Dortmund 1909, S.&nbsp;22 – Auszug aus dem Schatzbuch der Grafschaft Mark von 1486 (Bauerschaft Grevel/Kurl)</ref> Im Jahr 1705 waren in der ''Baurschafft Grevel'' 18 Steuerpflichtige mit Abgaben an die Rentei Unna im [[Kataster]] verzeichnet.<ref>''Westfälisches Schatzungs- und Steuerregister'', Band 6, Münster 1980. Darin: ''Kataster der Kontribuablen Güter in der Grafschaft Mark 1705'', Bearb. von Willy Timm, S.&nbsp;75</ref> Die Deutung des Ortsnamens bleibt unklar. Es ist mit einer Motivation durch eine Gegebenheit des Geländes, etwa einen Graben oder eine Grube, zu rechnen, die möglicherweise nicht mehr existiert.<ref>Michael Flöer: ''Die Ortsnamen der Stadt Dortmund und der Stadt Hagen'', in: Westfälisches Ortsnamenbuch, Band 16, Bielefeld 2021, S.&nbsp;105–108</ref> Im 19. Jahrhundert war Grevel eine Landgemeinde im [[Landkreis Dortmund]] und [[Amt Brackel]]. 1885 hatte die Gemeinde (plus 4 [[Wohnplatz|Wohnplätze]]) eine Fläche von 4,33 km², davon 241 [[Hektar|ha]] Ackerland, 19 ha Wiesen und 76 ha Holzungen. Es gab 53 Wohngebäude mit 67 Haushaltungen und 378 Einwohner.<ref>Gemeindelexikon für die Provinz Westfalen. Berlin 1887, S.&nbsp;80/81, Online-Ausgabe Münster, Universitäts- und Landesbibliothek 2014</ref> Grevel wurde am 1. April 1928 nach Dortmund eingemeindet.<ref>{{BibISBN|3402058758|Seite=237}}</ref> Der heutige Stadtteil Grevel ist dünn besiedelt und besitzt mit seiner Vielzahl an Bauernhöfen einen ländlichen Charakter. Zu den Sehenswürdigkeiten des kleinen Ortes zählen der [[Wasserturm]] ''[[Lanstroper Ei]]'' und der [[Luftschacht Rote Fuhr]] im Rahmen der [[Route der Industriekultur]] sowie die [[Alte Mühle Grevel|Alte Mühle]]. Zu Grevel gehört auch der Hienberg, der sich an die [[Deponie]] Nord-Ost anschließt. Die südlich anschließende Großsiedlung [[Scharnhorst-Ost]] entstand ab 1965 auf einem Areal, das bei Baubeginn noch zum Stadtteil Grevel gehörte, dann aber dem 1931 gebildeten Stadtteil Scharnhorst ([[Alt-Scharnhorst]]) zugeschlagen wurde. Grevel verlor durch die Großsiedlung zwar fast die Hälfte seiner Fläche, behielt durch die Abtretung jedoch seine ländliche Prägung. Im Gegensatz zu den meisten Dortmunder Stadtteilen stehen in Grevel noch eine hohe Anzahl an sehenswerten Fachwerkhäusern, und der Ort hat besonders an der ''Greveler Straße'' und dem ''Werzenkamp'' seinen dörflichen Charakter erhalten. == Bevölkerung == {{PanoViewer|Grevel Panorama 360.jpg|Grevel 360° Panorama}} Sozialstruktur der Greveler Bevölkerung: * Bevölkerungsdichte: 76 Einwohner pro [[Hektar]] Siedlungsfläche. * Minderjährigenquote: 17,7 %, leicht unterhalb des Dortmunder Durchschnittes. * Altenquote: 31,9 %, leicht oberhalb des Dortmunder Durchschnittes. * Ausländeranteil: 1,1 %, einer der niedrigsten Dortmunder Werte. * Arbeitslosenquote: 7,4 %, deutlich unterhalb des Dortmunder Durchschnittes. Das durchschnittliche Einkommen liegt etwa 20 % oberhalb des Dortmunder Durchschnittes. {| class="wikitable" style="text-align:center;" |+ Bevölkerungsentwicklung |- ! style="text-align:left;"| '''Jahr'''|| 2003|| 2008|| 2009|| 2013 |- |style="text-align:left;"| '''Einwohner'''|| 632|| 633|| 632|| 633<ref>{{Internetquelle|url=https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|titel=Statistikatlas 2015|hrsg=Stadt Dortmund – Stabsstelle Dortmunder Statistik|seiten=15|datum=2015-07|format=PDF; 24,2&nbsp;MB|offline=1|archiv-url=https://web.archive.org/web/20160914110041/https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|archiv-datum=2016-09-14|archiv-bot=2019-09-08 08:17:30 InternetArchiveBot|abruf=2016-06-29}}</ref> |} <gallery> Hienberg.jpg|Der Hienberg von [[Lanstrop]] aus gesehen Lei5.jpg|Der Wasserturm ''[[Lanstroper Ei]]'' BlickDerne.jpg|Blick vom Hienberg auf [[Derne]] und die [[Zeche Gneisenau]] Dortmund-Scharnhorst, Luftschacht Rote Fuhr, Totale.jpg|Der Luftschacht ''Rote Fuhr'' DSW405Grevel.jpg|Stadtbahn an der Endstation Grevel </gallery> == Verkehr == Grevel ist der nördliche Endpunkt der Linie U42 der [[Stadtbahn Dortmund]]. Sie verbindet Grevel alle zehn Minuten mit [[Scharnhorst-Ost|Scharnhorst]], [[Kirchderne]], [[Eving]], [[City (Dortmund)|Innenstadt]] und den südlichen Stadtteilen [[Barop]] und [[Hombruch]]. Am U-Bahnhof Grevel besteht ein Übergang zur Buslinie 423 nach [[Lanstrop]]. Es ist auch geplant, die U42 nach Lanstrop zu verlängern, um diesen Ort besser an die Innenstadt anzubinden. Dies soll aber erst langfristig umgesetzt werden. {| class="wikitable" |- class="hintergrundfarbe6" ! Linie ! Verlauf ! Takt {{Linienverlauf SPNV Rhein-Ruhr|U42}} |} Über die Anschlussstelle „Dortmund-Lanstrop“ ist Grevel an die [[Bundesautobahn 2]] und damit an das Fernstraßennetz angeschlossen, allerdings nur in Richtung Hannover. == Einzelnachweise == == Weblinks == * {{NWBib}} <references /> {{Navigationsleiste Stadtteile von Dortmund}} {{Normdaten|TYP=g|GND=4402970-6|VIAF=236097878}} [[Kategorie:Unterbezirk von Dortmund]] [[Kategorie:Ehemalige Gemeinde (Dortmund)]] [[Kategorie:Gemeindeauflösung 1928]] [[Kategorie:Ersterwähnung 1193]] rjgw1dsucjkthnwhm2xn63hcl3j0qei 739840 739839 2026-04-30T07:53:11Z NWBibBot 72927 Undid revision [[Special:Diff/739839|739839]] by [[Special:Contributions/NWBibBot|NWBibBot]] ([[User talk:NWBibBot|talk]]) 739840 wikitext text/x-wiki {{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Grevel | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.565 | Längengrad = 7.550 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 90 <!--www.tim-online.nrw.de--> | Höhe-Bezug = NHN | Fläche = 4.33 | Einwohner = 633 | Einwohner-Stand-Datum = 2013-12-31 | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 44329 | Postleitzahl2 = | Vorwahl1 = 0231 {{!}}- class="hintergrundfarbe1" {{!}} [[Statistischer Bezirk|Unterbezirk]]: {{!}} 221 | Lagekarte = | Lagekarte-Beschreibung = }} '''Grevel''' ([[niederdeutsch]]: Griëwel<ref>Wilhelm Schleef: ''Dortmunder Wörterbuch.'' 1967. XXII, 298 S. Ln. ([https://www.lwl.org/komuna/pdf/Bd_15.pdf lwl.org] PDF; 3,9 MB).</ref>) ist ein dorfähnlicher Stadtteil im [[Dortmund]]er Nordosten und gehört zum [[Stadtbezirk Scharnhorst]]. == Geschichte == Grevel wurde erstmals im Jahre 1193 mit ''curtes Marsuelde et Greuele et Wande'' urkundlich erwähnt. In den Jahren 1197 bis 1305 wird der Ort urkundlich ''Griuele, Greuilo, Greuele'' oder ''Grevele'' genannt. 1281 bis 1313 wurde ein ''Theodericus [[Ritter|miles]] dictus Vridach curtim in Grevele'' mit dem Ort [[Lehnswesen|belehnt]] sowie weitere Mitglieder der [[Adel]]sfamilie [[Frydag (Adelsgeschlecht)|von Frydag]] mit Grevel von 1317 bis 1400 belehnt. 1380 wird ein ''Thilemannus de Grevele'' im Dortmunder Urkundenbuch genannt. Im Jahr 1398 ein ''Johannes Grivel'' im [[Volmestein|Volmarsteiner]] Lehnsregister geführt. Grevel gehörte im [[Spätmittelalter]] und der [[Frühe Neuzeit|Frühen Neuzeit]] in eigener [[Bauerschaft]] (''Grevelkuylre'') im [[Kirchspiel]] [[Kurl]] und Amt Unna (historisch) zur [[Grafschaft Mark]]. Laut dem [[Schatzbuch der Grafschaft Mark]] von 1486 hatten in der Bauerschaft 22 Steuerpflichtige Hofbesitzer zwischen 1 oirt und 6 [[Rheinischer Gulden|Goldgulden]] an Abgabe zu leisten.<ref>[[Aloys Meister]]: ''Die Grafschaft Mark'', Festschrift zum Gedächtnis der 300-jährigen Vereinigung mit Brandenburg-Preußen. 2. Band, Dortmund 1909, S.&nbsp;22 – Auszug aus dem Schatzbuch der Grafschaft Mark von 1486 (Bauerschaft Grevel/Kurl)</ref> Im Jahr 1705 waren in der ''Baurschafft Grevel'' 18 Steuerpflichtige mit Abgaben an die Rentei Unna im [[Kataster]] verzeichnet.<ref>''Westfälisches Schatzungs- und Steuerregister'', Band 6, Münster 1980. Darin: ''Kataster der Kontribuablen Güter in der Grafschaft Mark 1705'', Bearb. von Willy Timm, S.&nbsp;75</ref> Die Deutung des Ortsnamens bleibt unklar. Es ist mit einer Motivation durch eine Gegebenheit des Geländes, etwa einen Graben oder eine Grube, zu rechnen, die möglicherweise nicht mehr existiert.<ref>Michael Flöer: ''Die Ortsnamen der Stadt Dortmund und der Stadt Hagen'', in: Westfälisches Ortsnamenbuch, Band 16, Bielefeld 2021, S.&nbsp;105–108</ref> Im 19. Jahrhundert war Grevel eine Landgemeinde im [[Landkreis Dortmund]] und [[Amt Brackel]]. 1885 hatte die Gemeinde (plus 4 [[Wohnplatz|Wohnplätze]]) eine Fläche von 4,33 km², davon 241 [[Hektar|ha]] Ackerland, 19 ha Wiesen und 76 ha Holzungen. Es gab 53 Wohngebäude mit 67 Haushaltungen und 378 Einwohner.<ref>Gemeindelexikon für die Provinz Westfalen. Berlin 1887, S.&nbsp;80/81, Online-Ausgabe Münster, Universitäts- und Landesbibliothek 2014</ref> Grevel wurde am 1. April 1928 nach Dortmund eingemeindet.<ref>{{BibISBN|3402058758|Seite=237}}</ref> Der heutige Stadtteil Grevel ist dünn besiedelt und besitzt mit seiner Vielzahl an Bauernhöfen einen ländlichen Charakter. Zu den Sehenswürdigkeiten des kleinen Ortes zählen der [[Wasserturm]] ''[[Lanstroper Ei]]'' und der [[Luftschacht Rote Fuhr]] im Rahmen der [[Route der Industriekultur]] sowie die [[Alte Mühle Grevel|Alte Mühle]]. Zu Grevel gehört auch der Hienberg, der sich an die [[Deponie]] Nord-Ost anschließt. Die südlich anschließende Großsiedlung [[Scharnhorst-Ost]] entstand ab 1965 auf einem Areal, das bei Baubeginn noch zum Stadtteil Grevel gehörte, dann aber dem 1931 gebildeten Stadtteil Scharnhorst ([[Alt-Scharnhorst]]) zugeschlagen wurde. Grevel verlor durch die Großsiedlung zwar fast die Hälfte seiner Fläche, behielt durch die Abtretung jedoch seine ländliche Prägung. Im Gegensatz zu den meisten Dortmunder Stadtteilen stehen in Grevel noch eine hohe Anzahl an sehenswerten Fachwerkhäusern, und der Ort hat besonders an der ''Greveler Straße'' und dem ''Werzenkamp'' seinen dörflichen Charakter erhalten. == Bevölkerung == {{PanoViewer|Grevel Panorama 360.jpg|Grevel 360° Panorama}} Sozialstruktur der Greveler Bevölkerung: * Bevölkerungsdichte: 76 Einwohner pro [[Hektar]] Siedlungsfläche. * Minderjährigenquote: 17,7 %, leicht unterhalb des Dortmunder Durchschnittes. * Altenquote: 31,9 %, leicht oberhalb des Dortmunder Durchschnittes. * Ausländeranteil: 1,1 %, einer der niedrigsten Dortmunder Werte. * Arbeitslosenquote: 7,4 %, deutlich unterhalb des Dortmunder Durchschnittes. Das durchschnittliche Einkommen liegt etwa 20 % oberhalb des Dortmunder Durchschnittes. {| class="wikitable" style="text-align:center;" |+ Bevölkerungsentwicklung |- ! style="text-align:left;"| '''Jahr'''|| 2003|| 2008|| 2009|| 2013 |- |style="text-align:left;"| '''Einwohner'''|| 632|| 633|| 632|| 633<ref>{{Internetquelle|url=https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|titel=Statistikatlas 2015|hrsg=Stadt Dortmund – Stabsstelle Dortmunder Statistik|seiten=15|datum=2015-07|format=PDF; 24,2&nbsp;MB|offline=1|archiv-url=https://web.archive.org/web/20160914110041/https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|archiv-datum=2016-09-14|archiv-bot=2019-09-08 08:17:30 InternetArchiveBot|abruf=2016-06-29}}</ref> |} <gallery> Hienberg.jpg|Der Hienberg von [[Lanstrop]] aus gesehen Lei5.jpg|Der Wasserturm ''[[Lanstroper Ei]]'' BlickDerne.jpg|Blick vom Hienberg auf [[Derne]] und die [[Zeche Gneisenau]] Dortmund-Scharnhorst, Luftschacht Rote Fuhr, Totale.jpg|Der Luftschacht ''Rote Fuhr'' DSW405Grevel.jpg|Stadtbahn an der Endstation Grevel </gallery> == Verkehr == Grevel ist der nördliche Endpunkt der Linie U42 der [[Stadtbahn Dortmund]]. Sie verbindet Grevel alle zehn Minuten mit [[Scharnhorst-Ost|Scharnhorst]], [[Kirchderne]], [[Eving]], [[City (Dortmund)|Innenstadt]] und den südlichen Stadtteilen [[Barop]] und [[Hombruch]]. Am U-Bahnhof Grevel besteht ein Übergang zur Buslinie 423 nach [[Lanstrop]]. Es ist auch geplant, die U42 nach Lanstrop zu verlängern, um diesen Ort besser an die Innenstadt anzubinden. Dies soll aber erst langfristig umgesetzt werden. {| class="wikitable" |- class="hintergrundfarbe6" ! Linie ! Verlauf ! Takt {{Linienverlauf SPNV Rhein-Ruhr|U42}} |} Über die Anschlussstelle „Dortmund-Lanstrop“ ist Grevel an die [[Bundesautobahn 2]] und damit an das Fernstraßennetz angeschlossen, allerdings nur in Richtung Hannover. == Einzelnachweise == <references /> {{Navigationsleiste Stadtteile von Dortmund}} {{Normdaten|TYP=g|GND=4402970-6|VIAF=236097878}} [[Kategorie:Unterbezirk von Dortmund]] [[Kategorie:Ehemalige Gemeinde (Dortmund)]] [[Kategorie:Gemeindeauflösung 1928]] [[Kategorie:Ersterwähnung 1193]] qdiumcdvq293ef8xnjf9qg26xqo2jbq 739841 739840 2026-04-30T07:53:36Z NWBibBot 72927 Bot: Ergänze Link zur NWBib mit NWBib Template 739841 wikitext text/x-wiki {{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Grevel | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.565 | Längengrad = 7.550 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 90 <!--www.tim-online.nrw.de--> | Höhe-Bezug = NHN | Fläche = 4.33 | Einwohner = 633 | Einwohner-Stand-Datum = 2013-12-31 | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 44329 | Postleitzahl2 = | Vorwahl1 = 0231 {{!}}- class="hintergrundfarbe1" {{!}} [[Statistischer Bezirk|Unterbezirk]]: {{!}} 221 | Lagekarte = | Lagekarte-Beschreibung = }} '''Grevel''' ([[niederdeutsch]]: Griëwel<ref>Wilhelm Schleef: ''Dortmunder Wörterbuch.'' 1967. XXII, 298 S. Ln. ([https://www.lwl.org/komuna/pdf/Bd_15.pdf lwl.org] PDF; 3,9 MB).</ref>) ist ein dorfähnlicher Stadtteil im [[Dortmund]]er Nordosten und gehört zum [[Stadtbezirk Scharnhorst]]. == Geschichte == Grevel wurde erstmals im Jahre 1193 mit ''curtes Marsuelde et Greuele et Wande'' urkundlich erwähnt. In den Jahren 1197 bis 1305 wird der Ort urkundlich ''Griuele, Greuilo, Greuele'' oder ''Grevele'' genannt. 1281 bis 1313 wurde ein ''Theodericus [[Ritter|miles]] dictus Vridach curtim in Grevele'' mit dem Ort [[Lehnswesen|belehnt]] sowie weitere Mitglieder der [[Adel]]sfamilie [[Frydag (Adelsgeschlecht)|von Frydag]] mit Grevel von 1317 bis 1400 belehnt. 1380 wird ein ''Thilemannus de Grevele'' im Dortmunder Urkundenbuch genannt. Im Jahr 1398 ein ''Johannes Grivel'' im [[Volmestein|Volmarsteiner]] Lehnsregister geführt. Grevel gehörte im [[Spätmittelalter]] und der [[Frühe Neuzeit|Frühen Neuzeit]] in eigener [[Bauerschaft]] (''Grevelkuylre'') im [[Kirchspiel]] [[Kurl]] und Amt Unna (historisch) zur [[Grafschaft Mark]]. Laut dem [[Schatzbuch der Grafschaft Mark]] von 1486 hatten in der Bauerschaft 22 Steuerpflichtige Hofbesitzer zwischen 1 oirt und 6 [[Rheinischer Gulden|Goldgulden]] an Abgabe zu leisten.<ref>[[Aloys Meister]]: ''Die Grafschaft Mark'', Festschrift zum Gedächtnis der 300-jährigen Vereinigung mit Brandenburg-Preußen. 2. Band, Dortmund 1909, S.&nbsp;22 – Auszug aus dem Schatzbuch der Grafschaft Mark von 1486 (Bauerschaft Grevel/Kurl)</ref> Im Jahr 1705 waren in der ''Baurschafft Grevel'' 18 Steuerpflichtige mit Abgaben an die Rentei Unna im [[Kataster]] verzeichnet.<ref>''Westfälisches Schatzungs- und Steuerregister'', Band 6, Münster 1980. Darin: ''Kataster der Kontribuablen Güter in der Grafschaft Mark 1705'', Bearb. von Willy Timm, S.&nbsp;75</ref> Die Deutung des Ortsnamens bleibt unklar. Es ist mit einer Motivation durch eine Gegebenheit des Geländes, etwa einen Graben oder eine Grube, zu rechnen, die möglicherweise nicht mehr existiert.<ref>Michael Flöer: ''Die Ortsnamen der Stadt Dortmund und der Stadt Hagen'', in: Westfälisches Ortsnamenbuch, Band 16, Bielefeld 2021, S.&nbsp;105–108</ref> Im 19. Jahrhundert war Grevel eine Landgemeinde im [[Landkreis Dortmund]] und [[Amt Brackel]]. 1885 hatte die Gemeinde (plus 4 [[Wohnplatz|Wohnplätze]]) eine Fläche von 4,33 km², davon 241 [[Hektar|ha]] Ackerland, 19 ha Wiesen und 76 ha Holzungen. Es gab 53 Wohngebäude mit 67 Haushaltungen und 378 Einwohner.<ref>Gemeindelexikon für die Provinz Westfalen. Berlin 1887, S.&nbsp;80/81, Online-Ausgabe Münster, Universitäts- und Landesbibliothek 2014</ref> Grevel wurde am 1. April 1928 nach Dortmund eingemeindet.<ref>{{BibISBN|3402058758|Seite=237}}</ref> Der heutige Stadtteil Grevel ist dünn besiedelt und besitzt mit seiner Vielzahl an Bauernhöfen einen ländlichen Charakter. Zu den Sehenswürdigkeiten des kleinen Ortes zählen der [[Wasserturm]] ''[[Lanstroper Ei]]'' und der [[Luftschacht Rote Fuhr]] im Rahmen der [[Route der Industriekultur]] sowie die [[Alte Mühle Grevel|Alte Mühle]]. Zu Grevel gehört auch der Hienberg, der sich an die [[Deponie]] Nord-Ost anschließt. Die südlich anschließende Großsiedlung [[Scharnhorst-Ost]] entstand ab 1965 auf einem Areal, das bei Baubeginn noch zum Stadtteil Grevel gehörte, dann aber dem 1931 gebildeten Stadtteil Scharnhorst ([[Alt-Scharnhorst]]) zugeschlagen wurde. Grevel verlor durch die Großsiedlung zwar fast die Hälfte seiner Fläche, behielt durch die Abtretung jedoch seine ländliche Prägung. Im Gegensatz zu den meisten Dortmunder Stadtteilen stehen in Grevel noch eine hohe Anzahl an sehenswerten Fachwerkhäusern, und der Ort hat besonders an der ''Greveler Straße'' und dem ''Werzenkamp'' seinen dörflichen Charakter erhalten. == Bevölkerung == {{PanoViewer|Grevel Panorama 360.jpg|Grevel 360° Panorama}} Sozialstruktur der Greveler Bevölkerung: * Bevölkerungsdichte: 76 Einwohner pro [[Hektar]] Siedlungsfläche. * Minderjährigenquote: 17,7 %, leicht unterhalb des Dortmunder Durchschnittes. * Altenquote: 31,9 %, leicht oberhalb des Dortmunder Durchschnittes. * Ausländeranteil: 1,1 %, einer der niedrigsten Dortmunder Werte. * Arbeitslosenquote: 7,4 %, deutlich unterhalb des Dortmunder Durchschnittes. Das durchschnittliche Einkommen liegt etwa 20 % oberhalb des Dortmunder Durchschnittes. {| class="wikitable" style="text-align:center;" |+ Bevölkerungsentwicklung |- ! style="text-align:left;"| '''Jahr'''|| 2003|| 2008|| 2009|| 2013 |- |style="text-align:left;"| '''Einwohner'''|| 632|| 633|| 632|| 633<ref>{{Internetquelle|url=https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|titel=Statistikatlas 2015|hrsg=Stadt Dortmund – Stabsstelle Dortmunder Statistik|seiten=15|datum=2015-07|format=PDF; 24,2&nbsp;MB|offline=1|archiv-url=https://web.archive.org/web/20160914110041/https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|archiv-datum=2016-09-14|archiv-bot=2019-09-08 08:17:30 InternetArchiveBot|abruf=2016-06-29}}</ref> |} <gallery> Hienberg.jpg|Der Hienberg von [[Lanstrop]] aus gesehen Lei5.jpg|Der Wasserturm ''[[Lanstroper Ei]]'' BlickDerne.jpg|Blick vom Hienberg auf [[Derne]] und die [[Zeche Gneisenau]] Dortmund-Scharnhorst, Luftschacht Rote Fuhr, Totale.jpg|Der Luftschacht ''Rote Fuhr'' DSW405Grevel.jpg|Stadtbahn an der Endstation Grevel </gallery> == Verkehr == Grevel ist der nördliche Endpunkt der Linie U42 der [[Stadtbahn Dortmund]]. Sie verbindet Grevel alle zehn Minuten mit [[Scharnhorst-Ost|Scharnhorst]], [[Kirchderne]], [[Eving]], [[City (Dortmund)|Innenstadt]] und den südlichen Stadtteilen [[Barop]] und [[Hombruch]]. Am U-Bahnhof Grevel besteht ein Übergang zur Buslinie 423 nach [[Lanstrop]]. Es ist auch geplant, die U42 nach Lanstrop zu verlängern, um diesen Ort besser an die Innenstadt anzubinden. Dies soll aber erst langfristig umgesetzt werden. {| class="wikitable" |- class="hintergrundfarbe6" ! Linie ! Verlauf ! Takt {{Linienverlauf SPNV Rhein-Ruhr|U42}} |} Über die Anschlussstelle „Dortmund-Lanstrop“ ist Grevel an die [[Bundesautobahn 2]] und damit an das Fernstraßennetz angeschlossen, allerdings nur in Richtung Hannover. == Einzelnachweise == == Weblinks == * {{NWBib}} <references /> {{Navigationsleiste Stadtteile von Dortmund}} {{Normdaten|TYP=g|GND=4402970-6|VIAF=236097878}} [[Kategorie:Unterbezirk von Dortmund]] [[Kategorie:Ehemalige Gemeinde (Dortmund)]] [[Kategorie:Gemeindeauflösung 1928]] [[Kategorie:Ersterwähnung 1193]] rjgw1dsucjkthnwhm2xn63hcl3j0qei 739842 739841 2026-04-30T07:54:00Z NWBibBot 72927 Undid revision [[Special:Diff/739841|739841]] by [[Special:Contributions/NWBibBot|NWBibBot]] ([[User talk:NWBibBot|talk]]) 739842 wikitext text/x-wiki {{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Grevel | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.565 | Längengrad = 7.550 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 90 <!--www.tim-online.nrw.de--> | Höhe-Bezug = NHN | Fläche = 4.33 | Einwohner = 633 | Einwohner-Stand-Datum = 2013-12-31 | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 44329 | Postleitzahl2 = | Vorwahl1 = 0231 {{!}}- class="hintergrundfarbe1" {{!}} [[Statistischer Bezirk|Unterbezirk]]: {{!}} 221 | Lagekarte = | Lagekarte-Beschreibung = }} '''Grevel''' ([[niederdeutsch]]: Griëwel<ref>Wilhelm Schleef: ''Dortmunder Wörterbuch.'' 1967. XXII, 298 S. Ln. ([https://www.lwl.org/komuna/pdf/Bd_15.pdf lwl.org] PDF; 3,9 MB).</ref>) ist ein dorfähnlicher Stadtteil im [[Dortmund]]er Nordosten und gehört zum [[Stadtbezirk Scharnhorst]]. == Geschichte == Grevel wurde erstmals im Jahre 1193 mit ''curtes Marsuelde et Greuele et Wande'' urkundlich erwähnt. In den Jahren 1197 bis 1305 wird der Ort urkundlich ''Griuele, Greuilo, Greuele'' oder ''Grevele'' genannt. 1281 bis 1313 wurde ein ''Theodericus [[Ritter|miles]] dictus Vridach curtim in Grevele'' mit dem Ort [[Lehnswesen|belehnt]] sowie weitere Mitglieder der [[Adel]]sfamilie [[Frydag (Adelsgeschlecht)|von Frydag]] mit Grevel von 1317 bis 1400 belehnt. 1380 wird ein ''Thilemannus de Grevele'' im Dortmunder Urkundenbuch genannt. Im Jahr 1398 ein ''Johannes Grivel'' im [[Volmestein|Volmarsteiner]] Lehnsregister geführt. Grevel gehörte im [[Spätmittelalter]] und der [[Frühe Neuzeit|Frühen Neuzeit]] in eigener [[Bauerschaft]] (''Grevelkuylre'') im [[Kirchspiel]] [[Kurl]] und Amt Unna (historisch) zur [[Grafschaft Mark]]. Laut dem [[Schatzbuch der Grafschaft Mark]] von 1486 hatten in der Bauerschaft 22 Steuerpflichtige Hofbesitzer zwischen 1 oirt und 6 [[Rheinischer Gulden|Goldgulden]] an Abgabe zu leisten.<ref>[[Aloys Meister]]: ''Die Grafschaft Mark'', Festschrift zum Gedächtnis der 300-jährigen Vereinigung mit Brandenburg-Preußen. 2. Band, Dortmund 1909, S.&nbsp;22 – Auszug aus dem Schatzbuch der Grafschaft Mark von 1486 (Bauerschaft Grevel/Kurl)</ref> Im Jahr 1705 waren in der ''Baurschafft Grevel'' 18 Steuerpflichtige mit Abgaben an die Rentei Unna im [[Kataster]] verzeichnet.<ref>''Westfälisches Schatzungs- und Steuerregister'', Band 6, Münster 1980. Darin: ''Kataster der Kontribuablen Güter in der Grafschaft Mark 1705'', Bearb. von Willy Timm, S.&nbsp;75</ref> Die Deutung des Ortsnamens bleibt unklar. Es ist mit einer Motivation durch eine Gegebenheit des Geländes, etwa einen Graben oder eine Grube, zu rechnen, die möglicherweise nicht mehr existiert.<ref>Michael Flöer: ''Die Ortsnamen der Stadt Dortmund und der Stadt Hagen'', in: Westfälisches Ortsnamenbuch, Band 16, Bielefeld 2021, S.&nbsp;105–108</ref> Im 19. Jahrhundert war Grevel eine Landgemeinde im [[Landkreis Dortmund]] und [[Amt Brackel]]. 1885 hatte die Gemeinde (plus 4 [[Wohnplatz|Wohnplätze]]) eine Fläche von 4,33 km², davon 241 [[Hektar|ha]] Ackerland, 19 ha Wiesen und 76 ha Holzungen. Es gab 53 Wohngebäude mit 67 Haushaltungen und 378 Einwohner.<ref>Gemeindelexikon für die Provinz Westfalen. Berlin 1887, S.&nbsp;80/81, Online-Ausgabe Münster, Universitäts- und Landesbibliothek 2014</ref> Grevel wurde am 1. April 1928 nach Dortmund eingemeindet.<ref>{{BibISBN|3402058758|Seite=237}}</ref> Der heutige Stadtteil Grevel ist dünn besiedelt und besitzt mit seiner Vielzahl an Bauernhöfen einen ländlichen Charakter. Zu den Sehenswürdigkeiten des kleinen Ortes zählen der [[Wasserturm]] ''[[Lanstroper Ei]]'' und der [[Luftschacht Rote Fuhr]] im Rahmen der [[Route der Industriekultur]] sowie die [[Alte Mühle Grevel|Alte Mühle]]. Zu Grevel gehört auch der Hienberg, der sich an die [[Deponie]] Nord-Ost anschließt. Die südlich anschließende Großsiedlung [[Scharnhorst-Ost]] entstand ab 1965 auf einem Areal, das bei Baubeginn noch zum Stadtteil Grevel gehörte, dann aber dem 1931 gebildeten Stadtteil Scharnhorst ([[Alt-Scharnhorst]]) zugeschlagen wurde. Grevel verlor durch die Großsiedlung zwar fast die Hälfte seiner Fläche, behielt durch die Abtretung jedoch seine ländliche Prägung. Im Gegensatz zu den meisten Dortmunder Stadtteilen stehen in Grevel noch eine hohe Anzahl an sehenswerten Fachwerkhäusern, und der Ort hat besonders an der ''Greveler Straße'' und dem ''Werzenkamp'' seinen dörflichen Charakter erhalten. == Bevölkerung == {{PanoViewer|Grevel Panorama 360.jpg|Grevel 360° Panorama}} Sozialstruktur der Greveler Bevölkerung: * Bevölkerungsdichte: 76 Einwohner pro [[Hektar]] Siedlungsfläche. * Minderjährigenquote: 17,7 %, leicht unterhalb des Dortmunder Durchschnittes. * Altenquote: 31,9 %, leicht oberhalb des Dortmunder Durchschnittes. * Ausländeranteil: 1,1 %, einer der niedrigsten Dortmunder Werte. * Arbeitslosenquote: 7,4 %, deutlich unterhalb des Dortmunder Durchschnittes. Das durchschnittliche Einkommen liegt etwa 20 % oberhalb des Dortmunder Durchschnittes. {| class="wikitable" style="text-align:center;" |+ Bevölkerungsentwicklung |- ! style="text-align:left;"| '''Jahr'''|| 2003|| 2008|| 2009|| 2013 |- |style="text-align:left;"| '''Einwohner'''|| 632|| 633|| 632|| 633<ref>{{Internetquelle|url=https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|titel=Statistikatlas 2015|hrsg=Stadt Dortmund – Stabsstelle Dortmunder Statistik|seiten=15|datum=2015-07|format=PDF; 24,2&nbsp;MB|offline=1|archiv-url=https://web.archive.org/web/20160914110041/https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|archiv-datum=2016-09-14|archiv-bot=2019-09-08 08:17:30 InternetArchiveBot|abruf=2016-06-29}}</ref> |} <gallery> Hienberg.jpg|Der Hienberg von [[Lanstrop]] aus gesehen Lei5.jpg|Der Wasserturm ''[[Lanstroper Ei]]'' BlickDerne.jpg|Blick vom Hienberg auf [[Derne]] und die [[Zeche Gneisenau]] Dortmund-Scharnhorst, Luftschacht Rote Fuhr, Totale.jpg|Der Luftschacht ''Rote Fuhr'' DSW405Grevel.jpg|Stadtbahn an der Endstation Grevel </gallery> == Verkehr == Grevel ist der nördliche Endpunkt der Linie U42 der [[Stadtbahn Dortmund]]. Sie verbindet Grevel alle zehn Minuten mit [[Scharnhorst-Ost|Scharnhorst]], [[Kirchderne]], [[Eving]], [[City (Dortmund)|Innenstadt]] und den südlichen Stadtteilen [[Barop]] und [[Hombruch]]. Am U-Bahnhof Grevel besteht ein Übergang zur Buslinie 423 nach [[Lanstrop]]. Es ist auch geplant, die U42 nach Lanstrop zu verlängern, um diesen Ort besser an die Innenstadt anzubinden. Dies soll aber erst langfristig umgesetzt werden. {| class="wikitable" |- class="hintergrundfarbe6" ! Linie ! Verlauf ! Takt {{Linienverlauf SPNV Rhein-Ruhr|U42}} |} Über die Anschlussstelle „Dortmund-Lanstrop“ ist Grevel an die [[Bundesautobahn 2]] und damit an das Fernstraßennetz angeschlossen, allerdings nur in Richtung Hannover. == Einzelnachweise == <references /> {{Navigationsleiste Stadtteile von Dortmund}} {{Normdaten|TYP=g|GND=4402970-6|VIAF=236097878}} [[Kategorie:Unterbezirk von Dortmund]] [[Kategorie:Ehemalige Gemeinde (Dortmund)]] [[Kategorie:Gemeindeauflösung 1928]] [[Kategorie:Ersterwähnung 1193]] qdiumcdvq293ef8xnjf9qg26xqo2jbq 739843 739842 2026-04-30T07:56:03Z NWBibBot 72927 Bot: Ergänze Link zur NWBib mit NWBib Template 739843 wikitext text/x-wiki {{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Grevel | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.565 | Längengrad = 7.550 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 90 <!--www.tim-online.nrw.de--> | Höhe-Bezug = NHN | Fläche = 4.33 | Einwohner = 633 | Einwohner-Stand-Datum = 2013-12-31 | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 44329 | Postleitzahl2 = | Vorwahl1 = 0231 {{!}}- class="hintergrundfarbe1" {{!}} [[Statistischer Bezirk|Unterbezirk]]: {{!}} 221 | Lagekarte = | Lagekarte-Beschreibung = }} '''Grevel''' ([[niederdeutsch]]: Griëwel<ref>Wilhelm Schleef: ''Dortmunder Wörterbuch.'' 1967. XXII, 298 S. Ln. ([https://www.lwl.org/komuna/pdf/Bd_15.pdf lwl.org] PDF; 3,9 MB).</ref>) ist ein dorfähnlicher Stadtteil im [[Dortmund]]er Nordosten und gehört zum [[Stadtbezirk Scharnhorst]]. == Geschichte == Grevel wurde erstmals im Jahre 1193 mit ''curtes Marsuelde et Greuele et Wande'' urkundlich erwähnt. In den Jahren 1197 bis 1305 wird der Ort urkundlich ''Griuele, Greuilo, Greuele'' oder ''Grevele'' genannt. 1281 bis 1313 wurde ein ''Theodericus [[Ritter|miles]] dictus Vridach curtim in Grevele'' mit dem Ort [[Lehnswesen|belehnt]] sowie weitere Mitglieder der [[Adel]]sfamilie [[Frydag (Adelsgeschlecht)|von Frydag]] mit Grevel von 1317 bis 1400 belehnt. 1380 wird ein ''Thilemannus de Grevele'' im Dortmunder Urkundenbuch genannt. Im Jahr 1398 ein ''Johannes Grivel'' im [[Volmestein|Volmarsteiner]] Lehnsregister geführt. Grevel gehörte im [[Spätmittelalter]] und der [[Frühe Neuzeit|Frühen Neuzeit]] in eigener [[Bauerschaft]] (''Grevelkuylre'') im [[Kirchspiel]] [[Kurl]] und Amt Unna (historisch) zur [[Grafschaft Mark]]. Laut dem [[Schatzbuch der Grafschaft Mark]] von 1486 hatten in der Bauerschaft 22 Steuerpflichtige Hofbesitzer zwischen 1 oirt und 6 [[Rheinischer Gulden|Goldgulden]] an Abgabe zu leisten.<ref>[[Aloys Meister]]: ''Die Grafschaft Mark'', Festschrift zum Gedächtnis der 300-jährigen Vereinigung mit Brandenburg-Preußen. 2. Band, Dortmund 1909, S.&nbsp;22 – Auszug aus dem Schatzbuch der Grafschaft Mark von 1486 (Bauerschaft Grevel/Kurl)</ref> Im Jahr 1705 waren in der ''Baurschafft Grevel'' 18 Steuerpflichtige mit Abgaben an die Rentei Unna im [[Kataster]] verzeichnet.<ref>''Westfälisches Schatzungs- und Steuerregister'', Band 6, Münster 1980. Darin: ''Kataster der Kontribuablen Güter in der Grafschaft Mark 1705'', Bearb. von Willy Timm, S.&nbsp;75</ref> Die Deutung des Ortsnamens bleibt unklar. Es ist mit einer Motivation durch eine Gegebenheit des Geländes, etwa einen Graben oder eine Grube, zu rechnen, die möglicherweise nicht mehr existiert.<ref>Michael Flöer: ''Die Ortsnamen der Stadt Dortmund und der Stadt Hagen'', in: Westfälisches Ortsnamenbuch, Band 16, Bielefeld 2021, S.&nbsp;105–108</ref> Im 19. Jahrhundert war Grevel eine Landgemeinde im [[Landkreis Dortmund]] und [[Amt Brackel]]. 1885 hatte die Gemeinde (plus 4 [[Wohnplatz|Wohnplätze]]) eine Fläche von 4,33 km², davon 241 [[Hektar|ha]] Ackerland, 19 ha Wiesen und 76 ha Holzungen. Es gab 53 Wohngebäude mit 67 Haushaltungen und 378 Einwohner.<ref>Gemeindelexikon für die Provinz Westfalen. Berlin 1887, S.&nbsp;80/81, Online-Ausgabe Münster, Universitäts- und Landesbibliothek 2014</ref> Grevel wurde am 1. April 1928 nach Dortmund eingemeindet.<ref>{{BibISBN|3402058758|Seite=237}}</ref> Der heutige Stadtteil Grevel ist dünn besiedelt und besitzt mit seiner Vielzahl an Bauernhöfen einen ländlichen Charakter. Zu den Sehenswürdigkeiten des kleinen Ortes zählen der [[Wasserturm]] ''[[Lanstroper Ei]]'' und der [[Luftschacht Rote Fuhr]] im Rahmen der [[Route der Industriekultur]] sowie die [[Alte Mühle Grevel|Alte Mühle]]. Zu Grevel gehört auch der Hienberg, der sich an die [[Deponie]] Nord-Ost anschließt. Die südlich anschließende Großsiedlung [[Scharnhorst-Ost]] entstand ab 1965 auf einem Areal, das bei Baubeginn noch zum Stadtteil Grevel gehörte, dann aber dem 1931 gebildeten Stadtteil Scharnhorst ([[Alt-Scharnhorst]]) zugeschlagen wurde. Grevel verlor durch die Großsiedlung zwar fast die Hälfte seiner Fläche, behielt durch die Abtretung jedoch seine ländliche Prägung. Im Gegensatz zu den meisten Dortmunder Stadtteilen stehen in Grevel noch eine hohe Anzahl an sehenswerten Fachwerkhäusern, und der Ort hat besonders an der ''Greveler Straße'' und dem ''Werzenkamp'' seinen dörflichen Charakter erhalten. == Bevölkerung == {{PanoViewer|Grevel Panorama 360.jpg|Grevel 360° Panorama}} Sozialstruktur der Greveler Bevölkerung: * Bevölkerungsdichte: 76 Einwohner pro [[Hektar]] Siedlungsfläche. * Minderjährigenquote: 17,7 %, leicht unterhalb des Dortmunder Durchschnittes. * Altenquote: 31,9 %, leicht oberhalb des Dortmunder Durchschnittes. * Ausländeranteil: 1,1 %, einer der niedrigsten Dortmunder Werte. * Arbeitslosenquote: 7,4 %, deutlich unterhalb des Dortmunder Durchschnittes. Das durchschnittliche Einkommen liegt etwa 20 % oberhalb des Dortmunder Durchschnittes. {| class="wikitable" style="text-align:center;" |+ Bevölkerungsentwicklung |- ! style="text-align:left;"| '''Jahr'''|| 2003|| 2008|| 2009|| 2013 |- |style="text-align:left;"| '''Einwohner'''|| 632|| 633|| 632|| 633<ref>{{Internetquelle|url=https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|titel=Statistikatlas 2015|hrsg=Stadt Dortmund – Stabsstelle Dortmunder Statistik|seiten=15|datum=2015-07|format=PDF; 24,2&nbsp;MB|offline=1|archiv-url=https://web.archive.org/web/20160914110041/https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|archiv-datum=2016-09-14|archiv-bot=2019-09-08 08:17:30 InternetArchiveBot|abruf=2016-06-29}}</ref> |} <gallery> Hienberg.jpg|Der Hienberg von [[Lanstrop]] aus gesehen Lei5.jpg|Der Wasserturm ''[[Lanstroper Ei]]'' BlickDerne.jpg|Blick vom Hienberg auf [[Derne]] und die [[Zeche Gneisenau]] Dortmund-Scharnhorst, Luftschacht Rote Fuhr, Totale.jpg|Der Luftschacht ''Rote Fuhr'' DSW405Grevel.jpg|Stadtbahn an der Endstation Grevel </gallery> == Verkehr == Grevel ist der nördliche Endpunkt der Linie U42 der [[Stadtbahn Dortmund]]. Sie verbindet Grevel alle zehn Minuten mit [[Scharnhorst-Ost|Scharnhorst]], [[Kirchderne]], [[Eving]], [[City (Dortmund)|Innenstadt]] und den südlichen Stadtteilen [[Barop]] und [[Hombruch]]. Am U-Bahnhof Grevel besteht ein Übergang zur Buslinie 423 nach [[Lanstrop]]. Es ist auch geplant, die U42 nach Lanstrop zu verlängern, um diesen Ort besser an die Innenstadt anzubinden. Dies soll aber erst langfristig umgesetzt werden. {| class="wikitable" |- class="hintergrundfarbe6" ! Linie ! Verlauf ! Takt {{Linienverlauf SPNV Rhein-Ruhr|U42}} |} Über die Anschlussstelle „Dortmund-Lanstrop“ ist Grevel an die [[Bundesautobahn 2]] und damit an das Fernstraßennetz angeschlossen, allerdings nur in Richtung Hannover. == Weblinks == * {{NWBib}} == Einzelnachweise == <references /> {{Navigationsleiste Stadtteile von Dortmund}} {{Normdaten|TYP=g|GND=4402970-6|VIAF=236097878}} [[Kategorie:Unterbezirk von Dortmund]] [[Kategorie:Ehemalige Gemeinde (Dortmund)]] [[Kategorie:Gemeindeauflösung 1928]] [[Kategorie:Ersterwähnung 1193]] sm25q8e9l1l6j6z11p5mz2b0ewhcxfr 739849 739843 2026-04-30T08:08:29Z NWBibBot 72927 739849 wikitext text/x-wiki {{Begriffsklärungshinweis}} {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Grevel | Gemeindeart = Stadt | Gemeindename = Dortmund | Alternativanzeige-Gemeindename = | Ortswappen = | Breitengrad = 51.565 | Längengrad = 7.550 | Bundesland = Nordrhein-Westfalen | Höhe-Präfix = ca. | Höhe = 90 <!--www.tim-online.nrw.de--> | Höhe-Bezug = NHN | Fläche = 4.33 | Einwohner = 633 | Einwohner-Stand-Datum = 2013-12-31 | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 44329 | Postleitzahl2 = | Vorwahl1 = 0231 {{!}}- class="hintergrundfarbe1" {{!}} [[Statistischer Bezirk|Unterbezirk]]: {{!}} 221 | Lagekarte = | Lagekarte-Beschreibung = }} '''Grevel''' ([[niederdeutsch]]: Griëwel<ref>Wilhelm Schleef: ''Dortmunder Wörterbuch.'' 1967. XXII, 298 S. Ln. ([https://www.lwl.org/komuna/pdf/Bd_15.pdf lwl.org] PDF; 3,9 MB).</ref>) ist ein dorfähnlicher Stadtteil im [[Dortmund]]er Nordosten und gehört zum [[Stadtbezirk Scharnhorst]]. == Geschichte == Grevel wurde erstmals im Jahre 1193 mit ''curtes Marsuelde et Greuele et Wande'' urkundlich erwähnt. In den Jahren 1197 bis 1305 wird der Ort urkundlich ''Griuele, Greuilo, Greuele'' oder ''Grevele'' genannt. 1281 bis 1313 wurde ein ''Theodericus [[Ritter|miles]] dictus Vridach curtim in Grevele'' mit dem Ort [[Lehnswesen|belehnt]] sowie weitere Mitglieder der [[Adel]]sfamilie [[Frydag (Adelsgeschlecht)|von Frydag]] mit Grevel von 1317 bis 1400 belehnt. 1380 wird ein ''Thilemannus de Grevele'' im Dortmunder Urkundenbuch genannt. Im Jahr 1398 ein ''Johannes Grivel'' im [[Volmestein|Volmarsteiner]] Lehnsregister geführt. Grevel gehörte im [[Spätmittelalter]] und der [[Frühe Neuzeit|Frühen Neuzeit]] in eigener [[Bauerschaft]] (''Grevelkuylre'') im [[Kirchspiel]] [[Kurl]] und Amt Unna (historisch) zur [[Grafschaft Mark]]. Laut dem [[Schatzbuch der Grafschaft Mark]] von 1486 hatten in der Bauerschaft 22 Steuerpflichtige Hofbesitzer zwischen 1 oirt und 6 [[Rheinischer Gulden|Goldgulden]] an Abgabe zu leisten.<ref>[[Aloys Meister]]: ''Die Grafschaft Mark'', Festschrift zum Gedächtnis der 300-jährigen Vereinigung mit Brandenburg-Preußen. 2. Band, Dortmund 1909, S.&nbsp;22 – Auszug aus dem Schatzbuch der Grafschaft Mark von 1486 (Bauerschaft Grevel/Kurl)</ref> Im Jahr 1705 waren in der ''Baurschafft Grevel'' 18 Steuerpflichtige mit Abgaben an die Rentei Unna im [[Kataster]] verzeichnet.<ref>''Westfälisches Schatzungs- und Steuerregister'', Band 6, Münster 1980. Darin: ''Kataster der Kontribuablen Güter in der Grafschaft Mark 1705'', Bearb. von Willy Timm, S.&nbsp;75</ref> Die Deutung des Ortsnamens bleibt unklar. Es ist mit einer Motivation durch eine Gegebenheit des Geländes, etwa einen Graben oder eine Grube, zu rechnen, die möglicherweise nicht mehr existiert.<ref>Michael Flöer: ''Die Ortsnamen der Stadt Dortmund und der Stadt Hagen'', in: Westfälisches Ortsnamenbuch, Band 16, Bielefeld 2021, S.&nbsp;105–108</ref> Im 19. Jahrhundert war Grevel eine Landgemeinde im [[Landkreis Dortmund]] und [[Amt Brackel]]. 1885 hatte die Gemeinde (plus 4 [[Wohnplatz|Wohnplätze]]) eine Fläche von 4,33 km², davon 241 [[Hektar|ha]] Ackerland, 19 ha Wiesen und 76 ha Holzungen. Es gab 53 Wohngebäude mit 67 Haushaltungen und 378 Einwohner.<ref>Gemeindelexikon für die Provinz Westfalen. Berlin 1887, S.&nbsp;80/81, Online-Ausgabe Münster, Universitäts- und Landesbibliothek 2014</ref> Grevel wurde am 1. April 1928 nach Dortmund eingemeindet.<ref>{{BibISBN|3402058758|Seite=237}}</ref> Der heutige Stadtteil Grevel ist dünn besiedelt und besitzt mit seiner Vielzahl an Bauernhöfen einen ländlichen Charakter. Zu den Sehenswürdigkeiten des kleinen Ortes zählen der [[Wasserturm]] ''[[Lanstroper Ei]]'' und der [[Luftschacht Rote Fuhr]] im Rahmen der [[Route der Industriekultur]] sowie die [[Alte Mühle Grevel|Alte Mühle]]. Zu Grevel gehört auch der Hienberg, der sich an die [[Deponie]] Nord-Ost anschließt. Die südlich anschließende Großsiedlung [[Scharnhorst-Ost]] entstand ab 1965 auf einem Areal, das bei Baubeginn noch zum Stadtteil Grevel gehörte, dann aber dem 1931 gebildeten Stadtteil Scharnhorst ([[Alt-Scharnhorst]]) zugeschlagen wurde. Grevel verlor durch die Großsiedlung zwar fast die Hälfte seiner Fläche, behielt durch die Abtretung jedoch seine ländliche Prägung. Im Gegensatz zu den meisten Dortmunder Stadtteilen stehen in Grevel noch eine hohe Anzahl an sehenswerten Fachwerkhäusern, und der Ort hat besonders an der ''Greveler Straße'' und dem ''Werzenkamp'' seinen dörflichen Charakter erhalten. == Bevölkerung == {{PanoViewer|Grevel Panorama 360.jpg|Grevel 360° Panorama}} Sozialstruktur der Greveler Bevölkerung: * Bevölkerungsdichte: 76 Einwohner pro [[Hektar]] Siedlungsfläche. * Minderjährigenquote: 17,7 %, leicht unterhalb des Dortmunder Durchschnittes. * Altenquote: 31,9 %, leicht oberhalb des Dortmunder Durchschnittes. * Ausländeranteil: 1,1 %, einer der niedrigsten Dortmunder Werte. * Arbeitslosenquote: 7,4 %, deutlich unterhalb des Dortmunder Durchschnittes. Das durchschnittliche Einkommen liegt etwa 20 % oberhalb des Dortmunder Durchschnittes. {| class="wikitable" style="text-align:center;" |+ Bevölkerungsentwicklung |- ! style="text-align:left;"| '''Jahr'''|| 2003|| 2008|| 2009|| 2013 |- |style="text-align:left;"| '''Einwohner'''|| 632|| 633|| 632|| 633<ref>{{Internetquelle|url=https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|titel=Statistikatlas 2015|hrsg=Stadt Dortmund – Stabsstelle Dortmunder Statistik|seiten=15|datum=2015-07|format=PDF; 24,2&nbsp;MB|offline=1|archiv-url=https://web.archive.org/web/20160914110041/https://www.dortmund.de/media/p/statistik_3/statistik/veroeffentlichungen/jahresberichte/Statistikatlas_2015.pdf#page=15|archiv-datum=2016-09-14|archiv-bot=2019-09-08 08:17:30 InternetArchiveBot|abruf=2016-06-29}}</ref> |} <gallery> Hienberg.jpg|Der Hienberg von [[Lanstrop]] aus gesehen Lei5.jpg|Der Wasserturm ''[[Lanstroper Ei]]'' BlickDerne.jpg|Blick vom Hienberg auf [[Derne]] und die [[Zeche Gneisenau]] Dortmund-Scharnhorst, Luftschacht Rote Fuhr, Totale.jpg|Der Luftschacht ''Rote Fuhr'' DSW405Grevel.jpg|Stadtbahn an der Endstation Grevel </gallery> == Verkehr == Grevel ist der nördliche Endpunkt der Linie U42 der [[Stadtbahn Dortmund]]. Sie verbindet Grevel alle zehn Minuten mit [[Scharnhorst-Ost|Scharnhorst]], [[Kirchderne]], [[Eving]], [[City (Dortmund)|Innenstadt]] und den südlichen Stadtteilen [[Barop]] und [[Hombruch]]. Am U-Bahnhof Grevel besteht ein Übergang zur Buslinie 423 nach [[Lanstrop]]. Es ist auch geplant, die U42 nach Lanstrop zu verlängern, um diesen Ort besser an die Innenstadt anzubinden. Dies soll aber erst langfristig umgesetzt werden. {| class="wikitable" |- class="hintergrundfarbe6" ! Linie ! Verlauf ! Takt {{Linienverlauf SPNV Rhein-Ruhr|U42}} |} Über die Anschlussstelle „Dortmund-Lanstrop“ ist Grevel an die [[Bundesautobahn 2]] und damit an das Fernstraßennetz angeschlossen, allerdings nur in Richtung Hannover. == Einzelnachweise == <references /> {{Navigationsleiste Stadtteile von Dortmund}} {{Normdaten|TYP=g|GND=4402970-6|VIAF=236097878}} [[Kategorie:Unterbezirk von Dortmund]] [[Kategorie:Ehemalige Gemeinde (Dortmund)]] [[Kategorie:Gemeindeauflösung 1928]] [[Kategorie:Ersterwähnung 1193]] qdiumcdvq293ef8xnjf9qg26xqo2jbq Fleier 0 175064 739845 2026-04-30T08:03:01Z NWBibBot 72927 Created page with "{{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Fleier | Gemeindeart = Stadt | Gemeindename = Dortmund | Breitengrad = 51.54708 | Längengrad = 7.58485 | Bundesland = Nordrhein-Westfalen | Höhe = 67 | Höhe-Bezug = NHN | Fläche = | Einwohner = 200 | Einwohner-Stand-Datum = | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 443..." 739845 wikitext text/x-wiki {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Fleier | Gemeindeart = Stadt | Gemeindename = Dortmund | Breitengrad = 51.54708 | Längengrad = 7.58485 | Bundesland = Nordrhein-Westfalen | Höhe = 67 | Höhe-Bezug = NHN | Fläche = | Einwohner = 200 | Einwohner-Stand-Datum = | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 44319 | Vorwahl1 = 0231 }} '''Fleier''' ist eine [[Ortslage]] in [[Dortmund]] mit etwa 200 Einwohnern, welche heute zum Stadtteil [[Kurl]] gehört. Das ehemals zu [[Asseln (Dortmund)|Asseln]] gehörende Flurstück wurde überwiegend seit den 1950er Jahren an die Bebauung Kurls angeschlossen und stellt heute den südlichsten Bereich dieses Stadtteils dar. == Lage == Der Ort liegt in ländlicher Umgebung am nordöstlichen Rand von Dortmund. Die Ortslage gehört zum [[Stadtbezirk Scharnhorst]]. Fleier liegt im Süden von Kurl an der Kurler Straße. Zum Ortsteil gehören die Häuser entlang der Langerohstraße sowie die einzelnen Häusergruppen an der Kurler Straße zwischen Kurl und Asseln. == Sehenswertes == Im anliegenden [[Naturschutzgebiet (Deutschland)|Naturschutzgebiet]] „[[Naturschutzgebiet Kurler Busch|Kurler Busch]]“ gibt es die verschiedensten Laubbäume wie zum Beispiel Stieleichen. Als Laubmischwald ist der Kurler Busch die Heimat vieler einheimischer Tier- und Pflanzenarten. Jährlich im Sommer findet das Traktorfest statt. {{Navigationsleiste Stadtteile von Dortmund}} {{Normdaten|TYP=g|GND=7780544-6|VIAF=208678051}} [[Kategorie:Stadtteil von Dortmund|Fleier]] 2iijft6rgnjag5cd2b3zth5writa3f6 739846 739845 2026-04-30T08:03:23Z NWBibBot 72927 Bot: Ergänze Link zur NWBib mit NWBib Template 739846 wikitext text/x-wiki {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Fleier | Gemeindeart = Stadt | Gemeindename = Dortmund | Breitengrad = 51.54708 | Längengrad = 7.58485 | Bundesland = Nordrhein-Westfalen | Höhe = 67 | Höhe-Bezug = NHN | Fläche = | Einwohner = 200 | Einwohner-Stand-Datum = | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 44319 | Vorwahl1 = 0231 }} '''Fleier''' ist eine [[Ortslage]] in [[Dortmund]] mit etwa 200 Einwohnern, welche heute zum Stadtteil [[Kurl]] gehört. Das ehemals zu [[Asseln (Dortmund)|Asseln]] gehörende Flurstück wurde überwiegend seit den 1950er Jahren an die Bebauung Kurls angeschlossen und stellt heute den südlichsten Bereich dieses Stadtteils dar. == Lage == Der Ort liegt in ländlicher Umgebung am nordöstlichen Rand von Dortmund. Die Ortslage gehört zum [[Stadtbezirk Scharnhorst]]. Fleier liegt im Süden von Kurl an der Kurler Straße. Zum Ortsteil gehören die Häuser entlang der Langerohstraße sowie die einzelnen Häusergruppen an der Kurler Straße zwischen Kurl und Asseln. == Sehenswertes == Im anliegenden [[Naturschutzgebiet (Deutschland)|Naturschutzgebiet]] „[[Naturschutzgebiet Kurler Busch|Kurler Busch]]“ gibt es die verschiedensten Laubbäume wie zum Beispiel Stieleichen. Als Laubmischwald ist der Kurler Busch die Heimat vieler einheimischer Tier- und Pflanzenarten. Jährlich im Sommer findet das Traktorfest statt. {{Navigationsleiste Stadtteile von Dortmund}} {{Normdaten|TYP=g|GND=7780544-6|VIAF=208678051}} [[Kategorie:Stadtteil von Dortmund|Fleier]] == Weblinks == * {{NWBib}} 5eha8txc3r44i9941z2aqygwinjsc2b 739848 739846 2026-04-30T08:08:01Z NWBibBot 72927 739848 wikitext text/x-wiki {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Fleier | Gemeindeart = Stadt | Gemeindename = Dortmund | Breitengrad = 51.54708 | Längengrad = 7.58485 | Bundesland = Nordrhein-Westfalen | Höhe = 67 | Höhe-Bezug = NHN | Fläche = | Einwohner = 200 | Einwohner-Stand-Datum = | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 44319 | Vorwahl1 = 0231 }} '''Fleier''' ist eine [[Ortslage]] in [[Dortmund]] mit etwa 200 Einwohnern, welche heute zum Stadtteil [[Kurl]] gehört. Das ehemals zu [[Asseln (Dortmund)|Asseln]] gehörende Flurstück wurde überwiegend seit den 1950er Jahren an die Bebauung Kurls angeschlossen und stellt heute den südlichsten Bereich dieses Stadtteils dar. == Lage == Der Ort liegt in ländlicher Umgebung am nordöstlichen Rand von Dortmund. Die Ortslage gehört zum [[Stadtbezirk Scharnhorst]]. Fleier liegt im Süden von Kurl an der Kurler Straße. Zum Ortsteil gehören die Häuser entlang der Langerohstraße sowie die einzelnen Häusergruppen an der Kurler Straße zwischen Kurl und Asseln. == Sehenswertes == Im anliegenden [[Naturschutzgebiet (Deutschland)|Naturschutzgebiet]] „[[Naturschutzgebiet Kurler Busch|Kurler Busch]]“ gibt es die verschiedensten Laubbäume wie zum Beispiel Stieleichen. Als Laubmischwald ist der Kurler Busch die Heimat vieler einheimischer Tier- und Pflanzenarten. Jährlich im Sommer findet das Traktorfest statt. {{Navigationsleiste Stadtteile von Dortmund}} {{Normdaten|TYP=g|GND=7780544-6|VIAF=208678051}} [[Kategorie:Stadtteil von Dortmund|Fleier]] 2iijft6rgnjag5cd2b3zth5writa3f6 739850 739848 2026-04-30T08:08:36Z NWBibBot 72927 Bot: Ergänze Link zur NWBib mit NWBib Template 739850 wikitext text/x-wiki {{Infobox Ortsteil einer Gemeinde in Deutschland | Ortsteil = Fleier | Gemeindeart = Stadt | Gemeindename = Dortmund | Breitengrad = 51.54708 | Längengrad = 7.58485 | Bundesland = Nordrhein-Westfalen | Höhe = 67 | Höhe-Bezug = NHN | Fläche = | Einwohner = 200 | Einwohner-Stand-Datum = | Eingemeindungsdatum = 1928-04-01 | Postleitzahl1 = 44319 | Vorwahl1 = 0231 }} '''Fleier''' ist eine [[Ortslage]] in [[Dortmund]] mit etwa 200 Einwohnern, welche heute zum Stadtteil [[Kurl]] gehört. Das ehemals zu [[Asseln (Dortmund)|Asseln]] gehörende Flurstück wurde überwiegend seit den 1950er Jahren an die Bebauung Kurls angeschlossen und stellt heute den südlichsten Bereich dieses Stadtteils dar. == Lage == Der Ort liegt in ländlicher Umgebung am nordöstlichen Rand von Dortmund. Die Ortslage gehört zum [[Stadtbezirk Scharnhorst]]. Fleier liegt im Süden von Kurl an der Kurler Straße. Zum Ortsteil gehören die Häuser entlang der Langerohstraße sowie die einzelnen Häusergruppen an der Kurler Straße zwischen Kurl und Asseln. == Sehenswertes == Im anliegenden [[Naturschutzgebiet (Deutschland)|Naturschutzgebiet]] „[[Naturschutzgebiet Kurler Busch|Kurler Busch]]“ gibt es die verschiedensten Laubbäume wie zum Beispiel Stieleichen. Als Laubmischwald ist der Kurler Busch die Heimat vieler einheimischer Tier- und Pflanzenarten. Jährlich im Sommer findet das Traktorfest statt. {{Navigationsleiste Stadtteile von Dortmund}} {{Normdaten|TYP=g|GND=7780544-6|VIAF=208678051}} [[Kategorie:Stadtteil von Dortmund|Fleier]] == Weblinks == * {{NWBib}} nvtxq8tx6yde6bip6e7khth09eo69c9