Wikipedia
testwiki
https://test.wikipedia.org/wiki/Main_Page
MediaWiki 1.48.0-wmf.1
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
Package
Package talk
CNBanner
CNBanner talk
Translations
Translations talk
Event
Event talk
Topic
Newsletter
Newsletter talk
Wikipedia:Test
4
71460
769866
769818
2026-10-23T22:30:35Z
~2026-56454-78
76373
/* Test */ Start
769866
wikitext
text/x-wiki
= Test =
1023
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (10:21)
''10:22''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
Current, 202 61007. ($#!+).
*202 61008. (High Speed Railway, Train)
''20261009''
28w7vz2ke8kgo4avucquq4qcj56ljph
769867
769866
2026-10-23T22:40:28Z
~2026-56454-78
76373
/* Test */ Start again
769867
wikitext
text/x-wiki
= Test =
1023 -
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (10:21)
''10:22''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
Current, 202 61007. ($#!+).
*202 61008. (High Speed Railway, Train)
''20261009''
2g08emtqawb1nqt41jrpk8d12smuoko
769868
769867
2026-10-23T22:50:36Z
~2026-56454-78
76373
769868
wikitext
text/x-wiki
= Test =
1023 - Airport
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (10:21)
''10:22''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
Current, 202 61007. ($#!+).
*202 61008. (High Speed Railway, Train)
''20261009''
sabrkadalgx0ad87886ko7lfwuxtyy0
769869
769868
2026-10-23T22:51:08Z
~2026-56454-78
76373
769869
wikitext
text/x-wiki
= Test =
1023 - List of Airport
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (10:21)
''10:22''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
Current, 202 61007. ($#!+).
*202 61008. (High Speed Railway, Train)
''20261009''
481f6wpmr0rklwbr1lvpdfaoztv3ih7
769870
769869
2026-10-23T22:51:29Z
~2026-56454-78
76373
769870
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (10:21)
''10:22''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
Current, 202 61007. ($#!+).
*202 61008. (High Speed Railway, Train)
''20261009''
fxiapmn621t86addtzxz5lynlz93m1i
769871
769870
2026-10-23T22:52:13Z
~2026-56454-78
76373
/* Test */ Done, Adding days now
769871
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
Current, 202 61007. ($#!+).
*202 61008. (High Speed Railway, Train)
''20261009''
adpovh4xitfawmvnaw6px8yx468vltz
769872
769871
2026-10-23T22:53:06Z
~2026-56454-78
76373
769872
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Basket, Coathangers and Others.
Current, 202 61007. ($#!+).
*202 61008. (High Speed Railway, Train)
''20261009''
s5fz8q1l36sa1844f5tmbxw7ppikrsu
769873
769872
2026-10-23T22:53:29Z
~2026-56454-78
76373
/* Test */ 9!
769873
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
Current, 202 61007. ($#!+).
*202 61008. (High Speed Railway, Train)
''20261009''
4vsdnwcvyyhuzwrdkmqd2ix26d09cx7
769874
769873
2026-10-23T22:54:06Z
~2026-56454-78
76373
/* Test */ 9 done
769874
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
Current, 202 61008. ($#!+).
*202 61009. (Driver Plates, Ls, Ps and Rs)
''20261010''
5c843vaoub8531d2brlhzypjrh6pf82
769875
769874
2026-10-23T22:54:57Z
~2026-56454-78
76373
/* Test */ 10
769875
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
Current, 202 61008. ($#!+).
*202 61009. (Driver Plates, Ls, Ps and Rs)
''20261010''
ckfcepw6evyi5m9780yrhocfoue9ut1
769876
769875
2026-10-23T22:55:46Z
~2026-56454-78
76373
/* Test */ 10 dobe
769876
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
Current, 202 61009. ($#!+).
*202 61010. (Binary Numbers, 0s and 1s)
''20261011''
0nmmz3endjlzbpluorjhpu8wehukxbj
769877
769876
2026-10-23T22:56:51Z
~2026-56454-78
76373
/* Test */ 11
769877
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
*202 61009, Towels, Black and White and Others.
Current, 202 61009. ($#!+).
*202 61010. (Binary Numbers, 0s and 1s)
''20261011''
7p5lrw9nbsplflsqgydm8ih2etdqqh2
769878
769877
2026-10-23T23:00:26Z
~2026-56454-78
76373
/* Test */ 11 done
769878
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
*202 61009, Towels, Black and White and Others.
Current, 202 61010. ($#!+).
*202 61011. (Mount Panorama Circuit for Bathurst 1000)
''20261012''
6mttcs7qxd7bqzbw8rb0qqup5cebdyg
769879
769878
2026-10-23T23:01:44Z
~2026-56454-78
76373
/* Test */ 12
769879
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
*202 61009, Towels, Black and White and Others.
*202 61010, Vacuum Cleaner and Others.
Current, 202 61010. ($#!+).
*202 61011. (Mount Panorama Circuit for Bathurst 1000)
''20261012''
mzwfi48mfix9yqn1l06ux8jo22znyti
769880
769879
2026-10-23T23:02:51Z
~2026-56454-78
76373
/* Test */ 12
769880
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
*202 61009, Towels, Black and White and Others.
*202 61010, Vacuum Cleaner and Others.
Current, 202 61011. ($#!+).
*202 61012. (World Map Wallpapers and Others)
''20261013''
fgy6j72ztb25e2dl6g9nplk1sjr3430
769881
769880
2026-10-23T23:03:33Z
~2026-56454-78
76373
769881
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
*202 61009, Towels, Black and White and Others.
*202 61010, Vacuum Cleaner and Others.
Current, 202 61011. ($#!+).
*202 61012. (World Map Wallpapers or Melways)
''20261013''
tdx8ehgdg4wn7klwldikfct69u6hcjt
769882
769881
2026-10-23T23:04:15Z
~2026-56454-78
76373
/* Test */ 13
769882
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
*202 61009, Towels, Black and White and Others.
*202 61010, Vacuum Cleaner and Others.
*202 61011, Blue Pens, Other Pens and Others.
Current, 202 61011. ($#!+).
*202 61012. (World Map Wallpapers or Melways)
''20261013''
0yjrsiqkbmxu6enoha3vg5s1mpa5pvo
769883
769882
2026-10-23T23:05:00Z
~2026-56454-78
76373
/* Test */ 13 done
769883
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14 (?)
*10:15 (?)
*10:16 (?)
*10:17 (?)
*10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
*202 61009, Towels, Black and White and Others.
*202 61010, Vacuum Cleaner and Others.
*202 61011, Blue Pens, Other Pens and Others.
Current, 202 61012. ($#!+).
*202 61013. (Restricted Area, Access Signs)
''20261014''
8u5tbs02frb5gpyv7blg4nl5he3g9r0
769884
769883
2026-10-23T23:05:40Z
~2026-56454-78
76373
769884
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:8, 242 days, (10:9+ to fill out).
*10:9, 10:10, 10:11, 10:12, 10:13 (?)
*10:14, 10:15, 10:16, 10:17, 10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
*202 61009, Towels, Black and White and Others.
*202 61010, Vacuum Cleaner and Others.
*202 61011, Blue Pens, Other Pens and Others.
Current, 202 61012. ($#!+).
*202 61013. (Restricted Area, Access Signs)
''20261014''
pglg9e25zylg0r4vcoqmv0vgsa5d2cr
769886
769884
2026-10-23T23:06:29Z
~2026-56454-78
76373
/* Test */ Add 5 more days
769886
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date
2:6 — 10:13, 247 days, (10:14+ to fill out).
*10:14, 10:15, 10:16, 10:17, 10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
*202 61009, Towels, Black and White and Others.
*202 61010, Vacuum Cleaner and Others.
*202 61011, Blue Pens, Other Pens and Others.
Current, 202 61012. ($#!+).
*202 61013. (Restricted Area, Access Signs)
''20261014''
qlf02stxaaveatzo6y5gcrgsjgbhana
769890
769886
2026-10-23T23:10:45Z
~2026-56454-78
76373
/* Test */ Done
769890
wikitext
text/x-wiki
= Test =
1023 - List of Airports
Candy Crush Saga Wiki: Tasty Sugar Track Edits Is Out Of Control (Mostly 6/23) and until 24 Restriction until no edits made all day, Use Account Sometimes.
Up To Date.
2:6 — 10:13, 247 days, (10:14+ to fill out).
*10:14, 10:15, 10:16, 10:17, 10:18 (?)
*10:19 (?)
*10:20 (?)
*10:21 (10:22)
''10:23''
*202 6525, Pot with Fork and Spoon, Bags with Sunglasses.
*202 6526, Baskets, Tobacco Stuff or Socks.
*202 6527, Food Shopping like Cola Bottles, Cans, Cordial and Others.
*202 6528, Socks, Pairs of Shoes, Dressing Gowns.
*202 6529, Towels, Cubes with Things.
*202 6530, Cloths, Flowers Frisun or Soap.
*202 6531, Food Shopping like Party Pies, Waters and Others.
*202 6601, Pairs of Boots, Pillow or Podiatrist.
*202 6602, Plates or Pans, Grated Cheese or Baskets.
*202 6603, Forks, Pans and Other Kitchen Tools.
*202 6604, Cups, Cordial Jug or Strawberry Milk.
*202 6605, Food Shopping like Cans of Coca Cola, Waters, Cordials or Pile of Sauce.
*202 6606, Purses, Lu Papers or Paper Towels.
*202 6607, Candles, Statues or Pizza Boxes.
*202 6608, Cups, Blankets and Others.
*202 6609, Food Shoppibg like Cola Bottles, Sour Cream and Onion Chips or Cans.
*202 6610, Towels, Cloths, Soap or Containers.
*202 6611, Nachos, Apple N Raspberry Codrial or Stationary.
*202 6612, Basket, Electric Bed with Instructions or Paint.
*202 6613, Aerobar, Birthday Cake or Packet of Candles.
*202 6614, Plates, Blankets or even Vacuum Cleaner.
*202 6615, Towels, Cloths or Dressing Gowns.
*202 6616, Smiths Chips, Heater Vents, Stuff for Lasagna or Cloths.
*202 6617, Pans, Table Spoons or Lids.
*202 6618, Food Shopping like Cans of Coca Cola, Waters, Milk and Others.
*202 6619, Chocolate Milk, Cups of Tea or Extra Mints.
*202 6620, Kitkats, Pens or Domino's Pizza.
*202 6621, Blankets and Pillows or Vacuum Cleaner.
*202 6622, Tea Towels, Cans of Coca Cola or Carlton Draught.
*202 6623, Food Shopping like Bottles of Cola, Waters or New Pyjamas.
*202 6624, Staples, Socks, Hats and Others.
*202 6625, Kitchen Bowls, Pans and Others.
*202 6626, Food Shopping like Colas, Milk or Rainbow Paddlepop.
*202 6627, Tablemats, Sunglasses or Garden Plant.
*202 6628, Boots, Sleep Medicine or Pillow.
*202 6629, Sewing Machine, Tobacco Stuff or Pan.
*202 6630, Food Shopping like Cans of Cocacola, Bottles, Kitkat or M&Ms.
*202 6701, BBQ Chips, Biskits or Grated Cheese.
*202 6702, Lighters, Plates or Cloths.
*202 6703, Hot Dogs, Cola Cans or Lasagna.
*202 6704, Mop, Battery Packs or Doorhandle.
*202 6705, Video Cameras, Internet or McDonalds Bag?.
*202 6706, Pans, Freshmint, or Hotdog Roll.
*202 6707, Food Shopping like Cans of Coca Cola, Cola Bottles, Waters, Shopping Bag or Grated Cheese.
*202 6708, Ktimes, Nutrigrain or Mueslibars.
*202 6709, Saladas, Socks, Vegemite or Textures.
*202 6710, Baskets, Favourites or Cordial Jug.
*202 6711, Shoes like Novo, Vacuum or Carlton Draught.
*202 6712, Aldi Stuff like Mints, Airfryer or Towel.
*202 6713, Food Shopping like Cola Bottles, Cheese, Milk or Paddle Pops.
*202 6714, Baskets, Mask Fresheners or Coat.
*202 6715, Towels, Cloths or Lasagna?.
*202 6716, Soaps, Toothpastes, Cotton Buds and Others.
*202 6717, Cans of Coca Cola, Bottles, Waters, Novo Shoes and Others.
*202 6718, Food Shopping like Upngo, Breads or Cans of Coca Cola.
*202 6719, Quichebox, Plate or Cup or Cheese Machine.
*202 6720, Cups, Milkshakes, Jug or Can of Coca Cola?.
*202 6721, Baskets, Coathangers, Chocolate Milk or Frypan?.
*202 6722, Food Shopping like Cola Bottles, Waters, Milk or Nachos?
*202 6723, Slippers, Blanket Sheets, Clothes Pair and Others.
*202 6724, Pans, Plates, Scissors or Tools.
*202 6725, Aldi Stuff, Domino's Pizza or Lynxspray.
*202 6726, Towels, Lighter or Journal Books.
*202 6727, Food Shopping like Bottles of Cola or Water, Cans or Milk.
*202 6728, Baskets, Battery Packs or Vaporizer and Others.
*202 6729, Sleep Medicines, Deep Heat or Pillows.
*202 6730, Schnitzels, Nesquiks, Milos and Others.
*202 6731, Toothpicks and Nuts and Others.
*202 6801, Food Shopping like Bottles, Cans, Waters and Others.
*202 6802, Plates, Bowls or Battery Packs.
*202 6803, Towels, Blankets, Purse or Boots.
*202 6804, M&M's, Baskets, Cloths or Coathangers.
*202 6805, Boots, Leaves, Texture and Others.
*202 6806, Food Shopping like Cans of Coca Cola, Nachos, Cheese or Milk.
*202 6807, Dressing Gowns, Glasses or Pillows.
*202 6808, Grater Cheese, Pizza, Mat or Plate.
*202 6809, Stylus, Mask, Soaps and Others.
*202 6810, Paper Towels, Staples, Lighter and Others.
*202 6811, Baskets, Cans of Coca Cola or Biskit Shapes.
*202 6812, Blackngold Cola, Blankets or Chocolatebox.
*202 6813, Smiths Chips, Cheese Mozarella or Bread.
*202 6814, Textures, Pens or Basket and Others.
*202 6815, Smoothies, Toys, Chopping Board or Box.
*202 6816, Cups like Gold, Cup of Tea, Metal Bottle and Others.
*202 6817, Food Shopping like Water Bottles, Cans of Coca Cola, Cola Bottles or Chocolate Milk.
*202 6818, Baskets, Keys or Cordial Jug and Others.
*202 6819, Aldi Stuff, Pizza or Pumpsoap.
*202 6820, Towels, Blankets Sheets and Others.
*202 6821, Cheese, Breads, Waters or Cans of Coca Cola and Others.
*202 6822, Sleep Medicone, Quichebox, Towels and Others.
*202 6823, Shoes, Hats or Ashtray and Others.
*202 6824, Cups, Milk, Towels or some Aldi and Others.
*202 6825, Battery Pack, Clothes and Others.
*202 6826, Cans of Coca Cola, Lime Cordial, Milk and Others.
*202 6827, Blankets, Pillows, Circle Table and Others.
*202 6828, Grated Cheese, Scissors, Knifes and Forks and Others.
*202 6829, Cans of Coca Cola, Bottles, Waters, Milk and Others.
*202 6830, Plates, Stand, Frypans and Others.
*202 6831, Metal Things, Water Bottles, Cans of Coca Cola and Others.
*202 6901, Aldi Bag with Stuff or Aprons.
*202 6902, Food Shopping like Cola Bottles, Cheese with Nachos and Others.
*202 6903, Mask Perfumes, Soaps and Others.
*202 6904, Baskets, Battery Pack, Pillows or Lasagna and Others
*202 6905, Frypans, Lids, Tablemats and Others.
*202 6906, Fathers Day Presents, Glasses, Bags and Others.
*202 6907, Deep Heat, Scissors, Milo or Container and Others.
*202 6908, Baskets, Coathanger, Blankets and Others.
*202 6909, Container Biscuit, Pastas or Pans.
*202 6910, Food Shopping like Milks, M&Ms, Sauces, Coca Cola Cans and Others.
*202 6911, Pillows, Dressing Gowns, Extra Mint and Others.
*202 6912, Laundry Stuff like boxes, bags or Dad's Wrist and Others.
*202 6913, Grater Stuff, Pans and Others.
*202 6914, Bottles of Cola, Breads, Milk to make Chocolate.
*202 6915, Iced Coffee, Water Bottles, Baskets and Others.
*202 6916, Aldi Bag, Strawberry Mints and Others.
*202 6917, Lighters, Plates, Freshener or Spray and Others.
*202 6918, New Clothes, Basket, Extra Mint and Others.
*202 6919, Face Cloths, Lu Paper, Water and Others.
*202 6920, Domino's Pizza, Pillows, Notes and Others.
*202 6921, Remotes, Pizza and Barbecue Shapes, Square Pillow and Others.
*202 6922, Food Shopping like Waters, Cans of Coca Cola, Cheese, Lasagna and Others.
*202 6923, Cloths, Facecloth, New Pot and Others.
*202 6924, Aldi Stuff, Glass Cups, Candles and Others.
*202 6925, Copy Paper, Glasses, Lasagna Tray Pan and Others.
*202 6926, Tax Boxes, Bags, Lighter, Texter and Others.
*202 6927, Cans of Coca Cola, Bottles, Cordial and Others.
*202 6928, Pairs of Shoes or Slippers, Square Pillow and Others.
*202 6929, Big Spoon and Others.
*202 6930, Cola Bottle and Others.
*202 61001, Yellow Bag and Others.
*202 61002, Coathangers or Towels and Others.
*202 61003, Knives, Forks and Others.
*202 61004, Hats like DNA hat and Others.
*202 61005, Pizza SHAPES and Others.
*202 61006, Old Pillow or Cola and Others.
*202 61007, Baskets, Coathangers and Others.
*202 61008, Plastic Cups and Others.
*202 61009, Towels, Black and White and Others.
*202 61010, Vacuum Cleaner and Others.
*202 61011, Blue Pens, Other Pens and Others.
Current, 202 61012. ($#!+).
*202 61013. (Restricted Area, Access Signs)
''20261014''
00hfla6uflnu1xogdyb3a3m6dl4z20r
User:John Vandenberg/async test write
2
81547
769934
769755
2026-10-24T10:25:33Z
Pywikibot-test
23124
Pywikibot 11.10.0.dev0
769934
wikitext
text/x-wiki
1792837523.8426054
q6smoezfe114e5wsk95vxr4ofcab5kp
User:John Vandenberg/appendtext test
2
81788
769933
769754
2026-10-24T10:25:23Z
Pywikibot-test
23124
769933
wikitext
text/x-wiki
1433650768.531433650922.331433651729.111433670166.311433675620.231434189298.391434190713.181434466819.82218051434544025.22135851434556196.60172341435173199.59346151435175699.59451271435227022.74044541435528701.75742911435530425.45590261435603389.080081437231694.72999551437247968.58793571437249806.3797741437298795.05681351437299498.54262071437301447.66920381437302227.7655441437303194.27986031437304101.72397661437310188.7074311437310950.0167771437312161.48824671437313010.34581731437313729.64502261437508795.02368621437509778.81783961437516631.40031891438256915.00195531438380338.75435851438383980.68647171438454064.30758831438457192.55998851438473558.4497151438475370.52737021438888563.82252121439048615.09364531439242551.86522051439284878.94054751439567068.00147221439645869.40761881439653355.34343361439892097.89366271439893989.92894151439937872.74017361439942911.25551271440071096.08264111440280762.25626281440718001.3262011440893319.42683861441040710.94389411441045094.56088661441367039.71286131441492543.00856571441494238.9647241443131370.03606581443212814.54482251454969098.621455143085.01455479939.331458010578.141667865282.52259731667886777.04896881667890384.16996771667891590.83226851667893076.91738251667895853.758761667903064.99619671667923005.8748951667928572.68390731667933784.59484671667953902.59001061667977923.4422121667980794.38768461667998421.35940931668010265.61172651668017452.821981668037264.28078271668060176.7066241668064765.02555161668074805.6843691668086298.94039751668146229.9122951668195362.96826151668202976.2808351668237144.36446861668334598.07503751668351702.72978881668355273.88250331668365870.03056961668380539.2045151668413384.79889971668425433.20089171668429428.62591481668429882.60904881668457667.70654341668457670.14254471668489259.42919141668489272.77256321668524787.90621761668619093.49428681668627855.24322251668715685.06132861668752284.64053461668846412.18063191668859062.45581651668861871.6680541668863624.53730581668906968.63125281668968346.827691669010411.7630731669034060.6490591669034135.1873461669111400.3310911669112565.91641261669115243.3388661669117792.36109231669147328.09415841669202489.4733361669207650.32954051669293250.78917381669293379.89944481669376126.44172071669383245.76502471669390928.33843281669456758.37305931669460552.29366331669466891.97368241669468483.16195921669507139.0193781669555832.00191161669561735.99639271669567236.94770341669575598.18926261669583250.82441161669589277.69016361669617066.74746061669623772.9824791669639778.26535651669648369.11541911669681417.00446221669694451.18411281669752632.77524021669790924.5213481669793446.41492681669806912.04391981669824036.82929231669872027.60449481669898227.71295671669898259.26809931669921155.98349361670061183.07913141670066507.8165841670077709.53290721670095550.83527471670107627.98861271670121136.95061851670138654.99623131670200104.08187131670230755.3226831670244520.57672931670244668.51782011670389975.82026081670390023.37481831670437142.29134321670474770.88995531670487246.87528041670502115.00278231670502920.1938131670847688.62029081670848465.75781231670851705.87951061671033970.25642091671040035.06844951671042864.6599261671372985.38839651671380314.9132991671397640.27274011671414714.3057121671436644.76151321671464740.28983191671471979.49879741671515351.36315011671534919.43988371671552136.73949381671567767.751751671633257.00196431671770826.36481481671831280.1013831671880464.16703251671892132.39851621671894047.95178441671895309.44157341671909181.81563191671915314.86987641671958262.78592091671964901.68418721672000091.18395731672040310.53217081672050418.50751851672064946.23232941672111553.61295031672117635.92343691672130442.62549381672242689.61853311672316734.83227941672317602.87131831672393398.53247791672410436.25166461672524792.27631931672607847.59926841672644463.87349151672646823.01231341672662689.7048611672663480.46076921672679608.50131681672683539.08552241672699544.1003541672732083.7852531672738571.41738751672756132.6532181672773164.29544851672819542.08460331672825014.19517781672905848.38628651672913462.50501871672919738.71067861672922430.75280711672923322.11899111672931497.2450511672932304.1778181672945954.7680341673031863.65857821673079844.9389911673088359.9833371673095552.19145581673097227.50064781673102532.33062361673103474.52100591673115502.80200391673170728.81979011673180084.83551031673186706.26281071673193236.3913051673204572.94682721673251372.26334931673257873.80217721673265157.1539221673268011.07930541673273333.41361331673278687.03390881673326768.0135041673336643.46683241673377550.0737211673406497.31679181673429347.03616951673442396.44323591673462031.40780231673475642.46197681673486662.78692481673494604.4536731673538992.12018081673597172.3338311673607749.57159781673612477.3083471673784155.33494331673792254.69903181673797192.45269541673805036.33327221673809787.90321951673820123.49186471673828737.7350961673845480.29922721673867388.29650971673871721.06576231673872705.55981831673881096.00600361673886092.39976671673901527.23546221673936700.1277911674024723.59655931674029565.17511731674112677.72348791674131134.36896941674131931.51858191674176788.94118571674205022.57874321674205892.4945881674207066.500241674237874.79511361674292792.7148671674300054.9405991674302649.25306581674315023.03331971674321156.4278271674330147.9499351674375033.1853471674379973.83156511674405785.2695661674460497.39998821674590174.95085531674599728.517341674918001.6330231674933771.13804131674942945.7791681674948667.64437911674994582.97465441675031208.62388441675082059.0771621675082116.2687371675107052.030031675108612.33305431675125215.25636861675257210.1793411675341484.38473531675341609.7052491675405564.5855741675453239.69066831675497461.10145021675528556.1533341675609308.2208911675687010.83857871675687111.48495861675814393.79852651675946118.90168211675946263.62637621676270319.42003041676413416.73733331676415638.60224561676550944.72917251676550988.86583191676811844.2470331676892937.27954441677156006.10806781677156012.4058921677404093.0326611677414534.3757681677422594.66884061677431827.26376751677502782.10921571677523601.2258471677532681.36643621677547559.68123271677665504.37648651677884342.66578171677941180.10126421677945698.08328151677946891.47642021678007696.49033051678013853.27689121678027514.350981678043805.73149591678052259.90557241678052643.0314441678106220.95231061678106343.42481541678133527.48043781678172110.71678571678365385.72825381678365409.99574831678764799.86219881678813325.91175081678813350.79962161679170545.65496781679450012.02785161679667672.31822281679744609.64862081679759956.30766031679761981.30153061679763749.89561941679767912.40684821679841243.24157331679932902.0535151679932917.83848141679940392.27980331679940406.82556631680053071.3510561680080183.73721581680082474.63970731680084259.96698951680086369.3228471680091872.83918521680118892.88375951680163589.5406531680163600.85161521680176046.1660681680176093.195821680203006.74859981680218432.4234941680218462.99551681680252349.87218141680259887.77778031680276784.21608421680363996.79403231680364030.12159371680461718.1685331680510128.81247231680523016.66201071680524491.84096381680535330.71197321680538614.16861461680540603.81350281680567879.27351781680584189.1618111680597522.02599571680611370.06081771680684863.62609741680702380.27745721680703768.80195071680724926.03246521680761509.16899231680781166.79312871680781181.85028271680802128.60378311680802968.58253961680810706.74496361680824056.68816421680858607.70792821680861050.77714251680866073.6807371680873834.65833931680876092.87447241680886191.0677981680893212.76874541680912955.40428971680931523.71085381680938348.89648771680946672.26258541680954488.31267331680957237.11845351680974132.6564441680981580.06852131680988103.15952011680998817.96175931681034799.03404331681040471.09055521681052004.54530671681126545.9002181681127117.2114981681132523.79209541681134369.85547611681139841.28229571681144411.1575561681148840.42879941681153937.74360161681160928.71128651681166905.73377011681187720.03183911681192405.11509941681227001.43019371681296338.53012781681298285.34920621681300548.25355361681399245.12391971681404166.32169871681450160.48757241681476226.65624211681482908.2132631681484907.63010051681488264.0226731681497922.02627541681516321.013621681550173.9751061681556873.35132771681634045.1093921681638556.73702431681643819.74579431681648142.90162351681654379.10618951681660670.91600231681663536.92180131681668105.8786741681697857.17983321681726412.36520551681728904.20325111681732377.95373061681732552.2726261681734122.47441771681739722.6026441681745392.19363141681752304.44942951681764534.25785851681834319.85117361681846787.53623871681860420.36393951681864973.31808471681875313.6030341681990891.22789741681990965.4691161682017923.75330021682021682.18149731682057250.76527521682059742.77901941682066226.93418961682076763.95251131682085198.28220251682144325.24995231682151599.1659521682171840.53873781682177076.96661421682183378.3197661682201845.24131771682207240.42407821682234649.83576371682237773.5109891682255562.43250821682256440.6314211682276533.21695041682307310.77357081682309188.4801751682327080.5300711682330112.66451981682335575.83368681682336670.2883251682364807.1775281682398596.78049471682412204.25284861682420579.50606181682438986.83246091682440038.89426971682460491.3521311682460514.15289761682486722.93688181682488779.60810041682526064.89231941682539359.5333161682594683.016191682595813.5718581682668022.43959161682669508.12358161682692147.2006271682757542.86185651682764905.5145551682772596.67823051682773643.5455011682784718.25319361682792198.62961861682846988.5308141682847360.08277031682855920.74403551682861011.37232731682866058.34553621682874046.51448271682874377.29042581682875782.05195261682880603.05094741682933872.46009541682935892.51180741682940017.87042071682941142.98671221682944638.7336221682968813.95042441683043013.224441683044388.7385881683079759.64020921683090931.4739611683122753.58097121683220168.91720751683220186.32398771683228402.15677021683229560.23437881683299138.82950661683545273.97475961683546542.5225811683639428.85272931683650636.34203671683805773.17751171683807026.16440961683929655.90975641683945251.3297731683945334.00193071683979199.57489941683989769.1559531684138199.31341811684138303.3588971684149417.94796661684150694.01225781684162796.8634221684166131.38370351684186491.90226461684251726.4504761684341513.18342381684408808.57671331684410199.48726771684489043.08548951684506392.8573881684675092.4285181684754267.71727821684755469.9380261684824482.14777731684830139.4911791684913767.22985341685013633.93379761685014938.77503231685102500.5862471685105187.50040251685186112.62153741685276024.9902461685359085.10708051685360365.34077381685452391.8990681685463350.18182641685464580.15679741685506240.55518721685507573.46218041685515091.31630541685618762.95992021685620191.40762351685686918.73273971685699127.00476841685700501.55954431685717478.4076761685734131.4306871685783480.31948381685784754.27991371685822284.17034241685826117.9924191685876410.60644561685899260.99922781685963819.95782521685965097.9749411686150851.77675581686223052.88230631686224433.3330841686232302.44289061686431729.6923871686484871.47790581686515043.2986561686569325.14197231686570601.30179381686829205.67923261686830619.36993621686858941.9247121686987477.50710231687163638.79776241687173445.39801721687174767.32710461687433296.5008811687434714.9197481687778668.3172251687780071.48142651687804721.06773691687804961.1013381687870703.48142171687880051.5818561687914862.05616451687945918.31335041687947395.06492521687953490.37763641687959324.18503831687975359.10666821687977529.40586021688005289.32374951688240380.11102061688282000.03003641688313546.3198141688475697.93261031688487946.15282921688516501.1861391688542375.87847381688542658.34382371688659572.67238471688989210.09367061689249540.64891221689516330.02128981689518572.78979041689583949.94965551689592901.34582541689607695.01970341689852040.93803671690180073.57848791690191138.51633981690321982.42092041690449914.8819191690455300.5934611690458478.94185231690465635.7540451690474689.47561761690539972.5845331690543686.42938161690568173.75836471690628964.38431071690634808.00196481690713940.84531521690740853.82095071690797462.8997041690802793.02018141690877446.66731241690889603.01329571690902576.47618631690960224.63360571690965952.47575381690985912.44080951690986558.72973781690988632.4453431691052503.35620671691059454.53480551691070774.57939621691155418.632231691252944.3855231691339134.81100341691400441.28892831691407321.69667531691432706.8650791691480503.51510481691481141.53991751691482868.38574171691653911.6456711691667067.58978751691677912.8174151691990311.52463151692012332.80747371692173850.40086171692259390.40197521692271662.9828511692354012.89279321692387939.37044841692450924.90117481692534492.76775311692541157.84960081692609941.4447311692613681.6871041692616885.06272461692862010.7047641692876369.23113751693053622.35440371693063667.55145881693149769.05626231693153142.6450571693216006.80971571693327074.38222051693400571.59087851693411769.74461581693475731.57202241693481027.00816321693491970.33835031693754058.26204921693826674.35946681694067456.52644161694086432.312941694086879.4216851694291429.4310971694431406.95781731694657927.28599861694659203.8303991694690891.68987661695036242.05242631695052511.6695451695073524.60868021695202102.93862631695206418.5803891695216347.58525681695221958.98424171695295594.74410271695320782.37095791695372272.756491695372628.97463751695378229.09218671695404004.23362351695634429.53836321695641055.01008581695761650.25719881695834309.91726141695927260.04524731695977453.164681695980141.0916121695994699.91543251696140618.86266831696336429.26674631696472860.44576241696505121.36730741696670716.8756681696689454.51658061696692541.47360921696850900.75192381697040191.60620881697097598.39965031697099236.2009691697144402.12478421697461393.63038371697527918.46235591697553804.78659531697815765.07235961697882531.84064671698051461.11684851698245633.10182791698508506.21113731698590537.26034331698771781.4928131698827472.40152741698827637.62499951698842362.84378241699091325.84301851699112379.118591699114243.11691121699184658.95015981699202708.55501941699205204.19041511699223592.85166761699283653.51198431699308697.88956881699632738.20176461699697040.08083271699706783.76506351699708540.52264861699878426.134871700605295.13594941700742428.27381941701453821.17849141701535213.18951851701596010.55240921701651782.60944631701676216.27204661701692980.01650791701786460.07426741701790394.30577541701794888.2957931701814293.08096051701829196.0474711701845925.83316681701848599.3431961701881057.82830721701954974.30711481701971095.83130741702043153.9835111702121022.79919271702122868.11675931702131140.8443211702209307.3005071702214096.37788411702297508.09620881702391057.6135691702476254.23142151702545723.33017161702556484.54818631702570523.91162061702584813.98265171702632851.84962561702767066.49533081702768384.77078131702786485.3929631702813757.61672471702976231.91704031702988046.03035161703007104.43871831703060641.49566441703061978.66786461703068432.47355061703161333.71216631703364778.24125841703400784.23487191703421297.88166571703422570.29016641703507209.10789851703681521.57480841703692553.249851703766399.7434331703852651.09494881703904819.15819121703906381.68961221703995904.11975221704121987.50157281704264435.08052851704282576.0567271704287308.40712621704298219.9210251704298836.44698121704303268.78285381704363509.4023921704370865.83305841704373191.9196831704446663.93405531704473306.82136271704556109.78341911704652663.27039841704703502.98144841705153078.2567631705171764.48118851705247919.4075471705321451.7373961705357661.07616421705461907.794251705752184.9362341705828818.60175251705830883.71437721705841015.54000781705844281.67964631705855451.01224971706185534.09338261706369476.87958551706371248.95242761706446636.12856911706448562.39597581706500837.33251021706622694.41761571706638400.31250291706643762.98329831706652657.88941431706698456.11485581706705858.3338771706775991.24169541706879091.59539581706959973.97395971706978466.78913071707041650.44565821707061227.6671281707135479.13930131707135774.83621931707138577.93408441707205349.07070681707210631.86285881707223177.35967421707234259.76761751707236462.89073851707298811.07813571707309485.17887141707324226.33939241707324395.90175751707324534.66345451707394969.43060231707473456.3788881707484017.54700971707509815.8565891707630003.67194221707650093.14672661707741016.75071951707843507.62747121707843972.63939711707891847.7828991707894042.62261721707894892.28781461708082572.46326521708270558.69651531708335825.3520781708343518.4265971708345382.65903431708346942.56566171708352754.41838361708355494.40021591708356544.03256871708357237.74094871708357678.17033841708523300.77632331708527433.11350181708597953.04801491708598048.15991881708600366.39798431708604576.4365961708610956.05604121708950178.1200671709014376.84691021709199311.14945361709209277.74335311709295953.42427751709372594.5523051709372731.93302561709372808.05139921709372973.60188251709374813.5571491709377109.42867421709484246.11830431709484785.2390241709533165.47048741709533819.17485741709534209.88706611709534926.47438381709538670.7195121709554858.88737061709556222.50437431709556406.85210971709564869.27713011709568887.47204641709573527.703281709629796.3196181709630611.95577671709630963.05990671709632191.8085871709632657.66816261709647467.98279861709660435.57397561709714867.27947381709717072.49129031709746877.2993021709805154.00563551709809459.23794721709814067.34054141709879953.99508521709884151.13086131709894214.31273981709902235.44790631709914532.75472951710159733.28755931710418921.42482541711628639.58727981711973782.74049451711974343.14418481711985794.7452041712014996.42285541712080693.69224641712155049.11888051712168399.60296921712169685.1040791712253703.75393061712502533.70237331712503293.1766331712505373.05488321712511056.70344231712511646.47766071712554342.8047431712578871.33526281712591147.02153471712593306.1449461712654643.9849731712672272.33918331712733238.39784071712773663.19977021712848308.0081541712860542.67349821713087332.46015721713121304.38284681713183849.22030931713372266.49107931713373136.88705831713417144.63786321713443071.2244371713541517.180411713541699.3184841713615294.52450591713619123.95877081713619984.8662581713620203.87909171713629353.7768171713693000.37563231713696953.16933941713701711.91550061713703518.66322571713708646.54834151713773912.73249241713776408.69481331713777146.3249111713784750.26938921713788549.0017581713803084.09800651713873378.93953251713874972.05580541713899910.39179831714006574.90925341714047778.38088851714214149.2920471714215604.62877371714218289.77900241714220302.95767071714231183.21794841714243696.78785441714305638.49526791714305731.21596931714311478.84540821714313996.51210671714485244.08302331714488946.5538881714652552.4470721714661325.34664131714665061.6320541714685108.3231281714709757.69277481714712589.13287451714717873.74175741714719260.34569121714908101.1871011714998038.7068751715004179.24228761715005017.33956381715011561.74028921715148898.65396171715244641.4409941715257241.7003561715271768.30176381715292152.86608841715330004.41626481715353713.42781571715353959.83065681715412668.46831631715421483.23257471715436334.75793931715520201.0295451715526648.21276971715527468.23268561715528972.9669821715529459.80643921715548182.89837461715602825.46457481715624287.64700171715666953.60590481715670258.04229781715681034.8107381715681373.08043051715862181.04882651715870345.8139851715870495.73219591715952418.20962721715953957.14005851716016439.23131821716026356.82077621716029493.3221591716031083.0943561716044060.71082471716107419.57978841716126517.3683791716131168.69529941716148891.67200921716149216.5821041716210413.7965631716312201.71627861716373672.36785031716374729.35419821716457578.06868461716467093.383061716559791.08338621716561161.53435441716649359.7539071716651785.40524051716723261.63200521716725596.59526781716732292.42133981716734447.8044291716738254.8956911716746654.24368071716795806.77896641716812563.0741851716892685.13354331716902147.31798151716903552.33264231716923921.77522521717066955.01724891717068917.04717661717072154.64399171717251267.23197651717259797.31061671717262953.80781751717328859.87784651717676565.94108441717861914.30391071717863020.84930681717927139.78493431717938355.69149681717953323.82942721717999765.11817811718009339.5952451718009454.02913121718009667.4100361718013654.53190851718016460.01057121718018761.15332411718020799.01913881718021245.15856621718025465.146841718026322.14862821718029950.58980631718041093.53587461718088975.1600171718098525.37702471718098991.82041171718099452.66476921718111343.16626361718114741.25173521718129965.94656441718167927.92333411718168560.61959651718170862.25188761718205019.47537661718206374.16787241718206647.1795651718208141.49481821718210905.61703281718270394.38123511718278706.33032561718287986.1937131718297994.59491591718336969.9835191718365449.34239961718449087.83051371718454283.21870951718456413.19333861718460153.46241951718462494.17207481718465987.28904911718467416.27678541718470343.79496341718477682.9318441718482373.43667821718515832.7972051718517249.02119351718517857.79042481718519972.8539831718522868.36958581718531550.47946141718536268.03831891718542101.57696131718544464.276851718619123.1634091718623185.88458681718636520.18447331718638800.48453331718731139.4622661718744170.95703461718774106.69617961718886170.088351718961712.35531521718968868.04600861718979695.67104221719060406.1220171719062040.21757941719067330.25522921719068089.06455061719076451.03764821719145795.36178421719151988.68255621719155248.8882151719162133.02871321719171629.7718651719242754.68141651719246705.8919081719255647.18871621719257347.07321881719298633.56316071719407710.83869461719491033.8175251719659127.47902351719661686.4932391719662640.99923061719664604.3216051719673418.46054081719677856.88689371719678098.51585631719678853.39937191719691556.0107241719694248.85178281719738870.07826381719739924.29021171719741111.60789011719742304.8520551719742716.78034661719745397.84074521719754662.89840941719760266.12941981719794972.6005541719825084.29679921719830253.9239311719836759.33914421719841637.71088621719842717.60566931719845321.64250181719847663.70107821719857525.72813531719993166.31000541720004409.5729961720018730.98016761720020182.87782071720076639.35904671720096106.61090231720113532.36414341720179036.01004391720265126.1817611720268654.5868861720282147.87318421720283309.32325241720286050.92650751720293539.3648961720348073.98572061720355917.47972461720362591.71961671720364407.29622911720429987.46009041720437485.89313361720441618.70600441720508195.45873951720518734.8392421720723289.42841981720871967.87330561720969642.51132971721145249.62304261721305348.6251181721379346.542811721406926.7735731721488540.2356821721496747.87938481721565527.91214751721574362.68826171721578502.43117141721580213.3311231721585283.3753051721628125.85017541721636883.68095471721651068.3836311721674353.60487581721711682.92611581721718456.25190661721736585.12386851721910443.05865841721911097.9222981721987871.73615431721989464.20908121722005684.10386231722009183.98562811722011469.2203711722014888.58855771722021975.44721461722083657.7424821722085815.53132181722096167.44949651722100696.7462071722149157.52232481722155656.36078931722163295.76613711722174676.3690631722242732.55940461722246601.0920391722252901.8107611722256046.73971531722258487.4373321722258901.9600681722263870.85462641722266409.4265291722267746.61000281722269621.92874881722269976.8815461722272080.77377251722279683.22267081722285241.02753781722286729.75908951722287985.7799481722299867.8714451722318171.4614381722346936.88357971722348798.46942781722350457.81067321722353627.2568861722357499.26558111722367575.99968431722373491.33078071722507532.24708371722515108.97734071722591849.9296011722703647.97711471722774371.71582841722860722.0833721722957642.21708731722965889.98333571722972359.45396161722984867.60560561723119754.11207841723125286.55064421723130127.51485061723134319.18638941723136059.16600351723137066.5406291723193843.56864331723198696.47698711723294038.37574581723295845.44823861723297533.78702881723303807.6631671723307181.10538461723311677.03078251723422079.64531681723457339.1525981723501078.41981941723503405.5784231723617569.60056951723639369.8436771723724588.10065871723789608.36872891723969341.08708981724070485.40928551724143436.30561071724158744.75811391724202777.0643011724301420.52415971724320167.54208281724326928.27127341724329651.45827651724339689.16775081724339990.81605431724341541.8335571724361752.17471031724675459.66274451724678562.51491741724679041.8596421724680019.75805571724740481.8159631724764160.67093681724772210.33077071724822514.46581531724931894.77922081725133285.29799841725140832.77638151725178025.79151321725179150.94023371725200755.48870351725202807.74458771725207502.73175141725208627.66635321725209216.05556461725210265.44690441725252365.3833031725281675.10662651725300399.91392231725540836.6651921725631556.60361741725822627.10982061725872571.50008231725884561.59261251726144055.4084121726302918.2109221726675560.78871441726731619.00652861726733403.45392541726734397.35323241726735635.69507741726747782.26425621726829193.35509751726830291.6796181726838130.2701831726852953.32955671726858668.94598631726904919.53113151726908011.78215241726911494.43632151726923822.73583151726924631.13999751726926654.4907061727094218.42981771727252570.93366581727281245.10181121727355214.64388871727355826.6178961727359034.26466751727360644.51546881727364225.8715341727365398.40147381727699176.51489191727760655.17109581727951236.031521727954405.64962171727957757.77329021727958178.89527631727959519.3105711728033650.33202581728045029.38966541728059516.23578881728060897.03357651728122955.8606541728129694.6456711728133522.16625431728139325.90391971728145836.68000821728194852.80935961728234083.04004381728241507.9161731728303752.22948271728337214.14192871728537254.20157051728732990.48960261728737796.45881251728739716.13933061728743550.084261728744005.3918461728745195.51385121728746301.37412671728747687.5570631728833762.35883451728835446.2922161728836591.77962781728837687.72793221728920301.63146951729092802.6129941729093469.09824441729093986.6930831729094596.3920461729096367.12394571729104951.39206271729127668.9245751729234880.59355141729257906.23548461729262780.74853061729333006.94400931729334137.91921041729336212.40924571729338537.22319481729339287.76037341729340708.85076141729342959.6031771729345579.34861141729352068.85322051729352424.3033391729355813.55383041729358559.69434331729367128.97711661729408008.7562531729410455.93828371729412778.66819211729447330.7101781729506929.79903321729550383.50321531729562047.72278021729772674.30436231729774243.54481581729788974.2966791729830363.88572481729956671.11215191729963276.13354231729964801.2759481730041059.24922511730042594.4898211730044967.31507661730052437.95801071730108706.1819951730118182.17060021730180022.8704031730203262.34906771730221702.35324031730275347.68835761730284724.74087071730285556.1908141730301942.04073381730374645.96319461730380770.90588331730404050.21319961730409146.01821851730413873.39013431730425842.61837341730477372.56358981730479560.97667481730551759.14125781730568814.75447271730572438.19093441730573549.2564731730579225.9238521730595578.01302431730646326.27951721730647944.81843971730737022.00499321730747490.06824851730807292.04786471730813368.64133481730818826.50661751730824558.75576351730832422.99776941730899305.0615351730901750.78711341730903102.00121621730971789.14266371730975168.8730961730982023.72336751731056436.06550221731059006.3753541731071324.57351161731148182.06626031731148462.44941141731155029.29867031731156907.49877831731167668.76448461731169795.64211731731171081.69893461731174489.80106231731229459.46800331731229698.89608241731233254.31718731731250434.28619031731481335.12306331731513305.40980241731593266.2619741731829257.91134291731859678.92372371731862005.49140671731922937.4808991731932247.01066061731932525.03579831731933290.09443261731935460.2790451731947768.1370441731963060.6405581731965814.97759751731997859.09550831732005512.74292661732031545.79197171732033235.34174281732036071.30061321732037983.13547731732041409.30651121732045107.55121021732263516.1040951732278310.46816371732288821.0565531732293225.38360171732383821.50968841732384069.83460351732391560.43771732433721.32781671732438713.17433931732441780.7008471732444579.79170161732451683.7766181732452619.78835271732457653.61890271732461172.61851021732467163.96287751732468839.23634151732469965.24506521732470239.99742651732470501.72090651732471055.29854061732520087.42624571732530123.6256731732536785.36732151732544673.93568091732553939.84375951732555010.63737251732562360.8003981732577912.6871631732579381.4846941732592567.86124521732594026.41688681732600123.13871861732605068.88938931732616986.9114541732619159.52321151732650631.55471561732720047.2405881732723299.8870821732728587.26730161732871729.11913281732948772.60584121732962637.1605561732975163.11275771732976290.6137441732985336.40084581732995963.96110731733080300.40849661733140107.68333481733155022.88794231733159221.4871291733168664.37563941733170027.81566981733170526.38246061733175309.14986661733219295.66059971733219681.74375251733224273.44269561733227643.3910411733228907.1991411733229826.99463961733234914.88147951733238777.20137861733242353.3587111733251189.9278881733255484.1180661733271589.04232071733293075.55299761733306828.96452671733309484.4286141733314197.22540021733315408.63991121733322593.26261733322805.56145381733333231.66890931733338012.36725621733557577.02903531733561313.32804731733565446.2815431733567333.90873651733567523.57054021733571119.16926811733573990.21949861733585274.73568651733585807.70079871733588017.7802691733593130.24049071733596018.95720121733609188.49137931733643746.43857721733650829.55517651733653518.41186571733656796.4539251733658330.072241733672789.5846831733673099.07036161733677665.48938061733732967.44802711733737559.55780841733739774.8239391733743548.28622531733743862.25829121733746858.26144361733748646.72557661733750237.3521711733761303.18399291733765178.02769851733822550.6699731733823340.01468631733826457.3003791733827822.37315151733833036.50665521733834839.1826851733836114.1890831733836604.02174851733840883.20230221733909333.22253161733909960.60128761734006546.8225221734015859.57260821734027149.3870951734036509.35217021734088813.24160461734092473.45144581734093224.58835391734159531.09753781734172373.82559251734177203.28432581734180637.15363741734189493.22910791734199074.094851734204520.1181031734215792.56901571734244208.42764541734262072.54748561734264107.63865041734268892.43243811734269365.42090751734271469.63599971734277423.22195431734290155.5247891734351728.73345951734367515.79722861734438331.95803671734439067.72983981734456547.34842541734461357.14716741734530139.5553771734588131.5415551734610976.74784561734693575.87978171734969824.56160281735013863.393161735215585.1146771735298797.99119881735300860.96658131735508440.68806081735560537.00754211735579463.11638711735594487.58908061735595948.66397571735597276.44437431735619132.0939121735711899.46031331735748282.272511735752879.14751721735771109.68907361735816480.08303861735818864.54984551735820574.48879741735845919.00464921735860298.96272641735905773.7076061735907315.48212431735920256.98129151735923066.94315771735927923.41648321735985947.44539171735987120.1806911735996630.83959561735997792.5823651735999950.93494461736005094.04602071736013959.72211721736091883.54373981736094621.82072661736109303.38124751736145589.5623011736158598.80606061736166007.97108551736240721.18058561736253866.97930461736259561.10710951736260639.09564881736326720.27908971736328441.73657161736441715.22370151736441948.95128971736448183.2816631736457756.60786081736667671.61953041736859448.04466371737053105.39502241737059294.32388951737218204.23131737230569.76234361737281191.78747031737308641.89546281737634796.05093771737661678.30621581737980414.56857161738176427.33152221738307157.1679331738509361.618441738511025.38118171738514914.97471881738516331.62643051738521441.32319571738525752.06775711738563699.85149531738565426.26452471738585137.30715081738597319.09383541738805958.55573341738865614.27569961739190092.90278551739725914.1677241739794922.60280971739988471.1611621740054329.53807951740079331.96067121740157595.89306021740220123.13252571740245134.83469081740306870.50918631740399755.20469451740424096.56160071740482149.7709031740504617.92714481740662913.58478671740698083.95707631740719951.12571741740758916.84956621740770227.28777481740838599.90834781740841180.05470851740846806.8733711740851730.32116771740864068.8188891740870144.50498751740909633.67035841740943265.17093921741538044.2799881741540280.86075281741615485.17051961742736965.52304531742812809.59154631742817938.266741742820838.3423121742833639.73128151742836382.87002781742890878.3034031742903231.48092751743006526.32749031743348943.7035481743704527.33931921744540122.66594081744542397.6184541744552562.3538921744553711.73710041745173335.05232761745238610.73597071745843195.26599981746226701.0918641746362227.3089311746371442.02811121746372752.96396041746447891.44518851746971136.59414241746972157.62861821746974457.8493371746976168.04008251747052875.46152851747480982.72086171747494506.47235631747495596.47780681747496096.58660771747501563.0468381747504104.17075041747506325.9508071747560842.93142221747572746.32942341747574020.4336941747574378.1243011747575177.46076971747579555.74680231747582533.39935111747582736.4808891747584502.74418571747590890.88166261747663855.65062121747762052.21985131748067768.55359531748070430.55718111748074382.30197071748084587.6802791748100243.06219581748262369.5918211748456997.30241161748502143.56055741748551733.06875921748559805.85630181748687418.24339271748692941.51643781748697135.5269361748702566.18927071748704892.67874261748707300.78871061748707718.06951361748710902.92918851748712630.29231881748772575.60379981748780746.9731851748787418.26495341748872035.61121321748878691.137971748896375.92971748900642.73245171749046627.53392841749098755.83221241749098959.20923261749099168.61557631749101223.54241181749119609.5599181749126667.21709511749136965.48203021749139791.37379071749205793.16769031749207480.8730421749207705.36759331749218861.07364541749271912.958471749280080.92087481749295870.75518011749309413.27836321749310948.2479821749322719.81444221749373678.24797731749375255.88597581749375475.07102351749390282.05390721749392292.0063081749411650.42451621749413965.12196371749475224.39129951749618675.85485481749622671.67921351749624056.94271451749627604.1784551749631181.01720761749632928.388051749633248.42225221749634804.01072031749635921.47261521749640795.06175831749655274.54887771749662043.67698741749712691.98407031749721876.21657251749732184.4471661749736626.7742331749739584.64724351749740402.61703251749747088.33462241749748466.53899031749750838.43557451749752305.12964371749783682.446221749811531.87015961749812424.43984911749815220.3855131749859800.50181271749861810.68900941749902117.18541221749904617.60818031749912253.1481761749913909.38116311750076587.25237131750112065.51739981750112549.35909461750113293.49248391750147068.9693321750147951.59855251750200400.22014521750319173.37273341750320656.22340271750335721.70233581750503005.99435231750505702.15230181750505889.32328081750516364.62822911750516682.09823581750520735.09064871750591072.01674371750619801.56257151750620371.7246161750622133.09173231750681332.28547381750940677.62030151751110771.93014931751115024.4489921751116291.45540211751189516.1089631751191203.62136581751197267.61229181751210591.699061751210808.46503661751286324.2863091751355187.47904781751545628.6297721751725864.76436571751730131.79488541751732922.95610931751740004.53788421751802693.42043191751853656.44941881751888514.45562741751977113.73002241751978375.45727251751980210.80578421751981578.71516081752053384.70437621752065147.01648551752066782.68813631752068878.81543951752205705.86502031752220216.62114951752246320.06772021752249909.7087711752317403.78078061752319959.82731491752406318.92591481752407259.95407271752421544.34403921752422710.55015231752425032.73874641752426826.2733981752428572.17700621752433706.0658221752470562.98645881752483585.27684041752484817.75912171752487219.72185131752581332.79816531752653249.8534491752655387.0071231752658130.9101921752659385.04600621752662426.3619591752668621.53872781752695270.23094871752755556.76991751752815643.08703021752824222.17550471752909383.60699961752912449.2338241752931172.30991841752932046.08516341752938425.21734791752979538.95104621753081962.70720271753090756.31310061753109911.16209861753527824.8208531753528157.58777211753528832.78555541753543391.409981753548006.04766771753555118.34784911753557058.28183871753607448.8085821753619533.97913721753621675.42673831753624357.04167771753628490.57589821753633928.4630891753634247.65696551753635835.1478181753637985.3761331753642657.74337051753705646.54954431753964966.3723731754036963.81071071754050075.4858231754051763.00968531754147508.76656821754147960.37546681754154766.89045071754161739.48721551754163737.6889791754210964.67577391754219537.72440741754222113.16079431754223091.64906881754223664.96145081754228602.35027861754229672.55927561754232636.3346941754233129.24810151754239954.64880471754241517.74627611754293618.73869231754310329.99401831754317864.02745131754318821.54698561754323102.69770261754326266.59494351754330431.04373651754334383.8229841754335714.09769081754394275.90666251754569461.6958781754638842.06614331754657332.2965881754661051.36486671754665334.8849631754718055.42017081754722367.38050371754836525.26578741754838348.94885041754838865.27615121754915063.0405931754922379.19625621754923244.57161741754929550.85405331754934157.59779951754989457.02608081754990760.36931751754998789.73707031755003472.47263961755091334.14983991755092562.45052361755103874.80169751755155650.49192211755157146.6223681755161682.15712331755166388.19822221755173045.7185681755174334.2213181755185779.42064291755186716.5619511755189286.1331981755242426.36476161755519872.06463531755528180.97155121755528528.62853481755528832.34833381755529449.05892421755529841.94665981755530424.19558621755531801.02439361755542591.2031921755546709.01237341755581286.38693331755683325.90864251755697406.07089071755767369.2794331755771352.36944061755779093.85620981755781792.09225491755786039.54400421755858394.51499061755864857.68235521755871908.5540961755872239.40110661755944972.05689141755963250.81905911755963438.96490861755965198.06602641755966671.70875931755967661.83248521756034035.27029371756051795.8273511756053720.38031081756062188.62394291756063534.1845211756068629.49149271756072172.11601281756125268.80325321756151453.8187271756310124.8685731756324844.69694781756482129.77022931756490613.15560961756492041.12433431756492297.4743671756513623.957081756514647.05277421756543727.9926171756545188.08750371756554689.8216321756560330.4979361756563740.7113121756565274.90660831756568027.52632161756623368.20361261756651846.51766971756657428.71791361756663741.77574421756668491.29928161756688997.64640711756705263.30104331756730683.43084431756812503.3911331756816382.50912021756880677.25349831757250586.60078881757252044.00713371757254258.84415481757315748.36797551757406692.55116631757427558.33150171757522892.84468961757593511.33569531757676782.52932741757759889.01814961757767620.0571711757769226.14481571757860829.75224731757861011.95556551757869302.40287541757870842.21012851757936618.8545211758094931.5402841758198530.53288751758272178.07883481758376917.19157841758379331.32556821758383837.43758921758384955.91952871758386059.87447021758387594.98469191758436846.8173221758444026.71285961758465789.753581758469660.50086121758470168.84960561758473722.39771131758789360.21657351758790715.03019021758803464.58468131758988044.04686881758991604.67435341759149102.85564831759492772.7183581759678936.99280551759680244.41957191759682618.7215971759997215.5191141760065958.82141760108993.57618381760110235.74441121760113550.87181661760177011.020731760181782.93854881760187005.60134651760192106.0860061760197207.4219841760199258.14967731760201985.65009261760203562.60572621760208440.80056051760260714.59390331760269934.64895941760285343.12197661760302129.65267131760328991.23364641760347755.98690061760350649.9220261760358687.67830591760362798.8370321760370432.53595111760424926.0255061760743184.63201121760744311.27082821760786622.23636051760788165.03988481760793302.1477371760793581.00444651760860182.22760991760863110.1014251760869362.4851031760869631.20522211760938861.72963521760972576.6875791761060407.88111571761060627.86563541761144889.48445651761148094.4665371761149201.68041321761219314.77552221761222483.27736571761295138.05956271761300300.05976561761311514.2513831761383799.72652271761391837.57163881761470800.4115141761501864.5615621761505443.42185241761568627.8543231761574494.27752451761580013.00882581761732663.23955371761733540.29735681761734029.47098021761736371.18000411761739191.4154661761739705.32874251761753667.99567171761756863.44928861761784016.66543271761815295.3384011761818717.73632571761828134.65532541761903405.21529581761907058.3059471761907853.07356211761916217.91197681761918750.98722891761919810.46501971761924888.20557641761931309.01599481761932455.5341991761943258.13832351761943965.9173671761998689.14334371762002422.82314631762003826.12135481762004604.3158771762010514.22436361762011210.89124921762016597.03093961762018766.23459271762020096.98681351762101868.87471441762105814.20747041762117386.51707461762172536.0202071762175528.28611851762182329.89773131762184577.847421762200953.02406841762253192.46915271762287934.7201151762422643.67606431762522533.03694341762528028.09290721762604673.09312731762616332.89323471762622168.34792021762622653.81842371762623239.4300881762685869.81218481762686662.3993391762687533.77702381762690043.77134161762690640.88656691762695418.03792051762709145.42883351763297104.580981763299241.28969961763302424.11725041763302843.86228871763303656.57493881763308095.12544541763314434.7122511763369939.32778621763382952.35389781763386880.2573951763389202.88072281763392117.76956531763546287.4585961763642087.21390341763659970.05781651763789967.2350981763795214.27324411763919897.74203251763924530.93485471763952917.04675961763987168.7900181764011135.03248621764218317.51825621764340352.97474771764360919.5237561764363105.4042141764526965.08457641765187128.96606761765355457.20101881765360544.06178261765361150.35600881765365518.7660051765557551.37651371765634926.0382451765640618.16870021765643253.3146741765643519.69737031765644942.7686081765650850.12580161765723889.33895751765725272.58981011765801621.52776551765879692.79019981765881108.2923851765890266.88562971765904510.36874341766061002.58671021766125878.0924161766175231.09851151766925374.73368381766996893.3731451767005790.16068861767008228.98308441767008524.3519591767008722.277691767009786.6732971767010541.95609551767013276.2328151767014306.68311551767015117.66693161767016373.33748561767086094.86997441767091943.21654251767092201.60667631767092814.322511767093453.33107881767270242.21536641767408827.13386231767450501.75768571767523607.15288781767524436.25480221767526671.70246891767540425.50893971767603172.05479241767608491.66707541767609798.80080491767780009.40121251767781869.36996481767783204.39632751767790049.159871767794832.20550161767797154.26203081767804637.35404421767805344.88099361767806485.79580351767862570.84585951767875104.49013021767876387.1385631767878636.14207961767880873.36738281767886064.7900161767887927.80501461767888628.77220651767894802.7501641767896425.17220471767954580.81757711767965181.28974031767970332.55244421767976705.94087121767978150.31396751767979970.38509061767982341.54357241768045908.30165961768048075.19306331768053087.31861191768053335.17539641768053645.28298431768057005.33032661768066054.97335241768079709.17248341768086210.00448131768134738.7489611768294958.7072611768308509.28162381768315525.73615461768317971.54583171768324985.50381061768384450.31180241768384903.74312451768388680.90803861768392012.94858881768393585.3805781768393995.64151621768397007.99611141768406785.83768421768407489.64632871768409265.181261768551967.76439951768562217.99268051768610315.5459591768648558.153921768750832.0425951768752477.22225881768754282.39400241768796055.877771769188973.98886591769356219.91804651769431235.54304531769520922.91533951769599853.44837931769601389.59619861769602998.44604521769608654.73905181769610587.53120471769613698.73730281769726072.5167361769754197.50043941769755012.88609891769788320.73522931769858771.11245351769880002.6400221769942094.34304521769950759.62865611770017332.54535651770029686.73525791770030911.36921741770035196.27489521770078295.91910531770155819.04716871770156062.72328571770198607.2284711770216427.27850631770217725.82147841770285886.35344841770290884.90398961770293022.25364641770293820.08989361770294246.49858431770296115.1938441770299476.89053541770304820.12695341770352845.59949111770360288.23392491770367647.6183741770375651.93329671770377722.19474721770388368.43299151770448360.88149361770474728.24455641770481662.81833741770482589.88003921770488483.83722141770547899.68135141770552992.3148081770630929.81703331770633593.05083631770639855.45341871770641556.83762381770666283.9656191770678866.937431770713538.44122551770808102.5597981770808949.6446791770809499.2195741770810416.91006471770899285.98089671771076383.70956751771153651.76371071771164919.83956381771165092.99137471771174054.22143321771175340.04150221771177045.09119531771185402.94351391771244500.35640861771600069.33150391771684144.1958011771757051.82674771771764404.84740111771780335.22727061771837797.45328641771839342.36841251771972863.80090481772004107.47609931772039629.94084841772097117.20003531772109451.20020151772125675.95705031772125857.190021772183872.96083351772280443.39041772295620.44625071772297315.33010551772298006.16521671772298862.35013271772328483.23607921772383354.17270871772383859.67349171772453976.5166041772713474.08007761772764991.76435041772787200.42355971772810020.21219421772905521.24598431772967859.26760581772984277.1005431772986433.85609721773059860.61029891773145094.80025391773145911.42583251773209770.56137851773327542.94483571773342123.46900181773389597.40498381773414356.9681911773416039.479741773416217.71348381773419126.79226261773433373.38669231773436829.7128131773496559.35769411773508591.08885051773526120.85893461773531469.85906171773532861.86214071773533352.6592681773664068.5733641773923117.08077051774116655.5106381774122638.22514531774123641.88898591774125088.17211991774165485.80263611774173634.33903151774178999.34086061774183816.01904231774191744.71987271774268550.01723621774527788.46656441774873687.7349881774875923.54350611774876751.77354171774880838.67538051774881356.113411775230959.73582771775232675.9200561775235024.41129371775242021.88784241775312892.665391775318498.7480911775319347.16049271775322263.85051471775381212.6245531775402348.91712781775462820.18094781775464128.60385511775478288.9307221775492883.68803621775494355.61733411775551100.70342451775737468.23703461776083199.4409181776342248.38125251776343741.35873751776344656.84962031776344823.2718941776494663.58264471776500579.0558131776513568.45815661776515798.23732261776521467.21749231776525755.44738361776530770.69265341776586552.67150241776602163.0317081776602999.49911571776688068.9822271776732694.04593831777121040.04677321777124071.1219561777126020.42327551777134818.39440461777287314.63329941777293598.26220231777363083.66518071777893423.46401861777895088.46678451777895860.43515851777897093.3858841777906171.800171777971134.0487721778058288.40560981778240440.64545181778329365.76004721778330451.60289481778332821.17423941778334531.54666111778342595.05410931778399875.18376181778419689.60360841778424127.0708861778487982.02455741778490235.47887951778512722.16216681778513868.5482841778514150.02312251778579208.82636931778579750.70454621778592818.33435651778593837.24621341778690465.584721778695987.13411741778697526.6658811778761332.9720331778822349.00370981778843702.19216871778918473.19561221778921342.1787031778931726.05583721778934249.29012421778934437.7146981778937818.14839361778942880.65417721778943688.6406221778945937.1907251778948525.945271779010660.48089271779012402.8849981779013545.70784571779014082.44581171779018379.58577371779019598.37050581779031500.34276341779032724.80927351779036530.4997151779039779.62068941779093071.12585621779097835.65313321779098505.47838691779100146.64374641779105919.20712781779107140.5768891779176813.17461751779177591.00395081779179103.9485821779179883.46228861779185903.12827781779188010.30328131779188332.78576521779199711.64825681779291274.87059261779375116.30314351779711775.9329841779718782.88222651779719462.58929511779720087.20665341779729556.75550171779729838.27185651779809157.15284131779811618.10328821779819147.38901331779821301.30405041779866487.12891531779872294.22598481779971079.62741731780155829.02630381780156437.50365951780156947.1906751780160264.74311021780167393.25985981780170098.30806231780172747.63186741780387294.64188241780398187.88314941780404797.27766011780815769.00067281780829125.25964361780848886.74698451780921520.99491741781093734.38310651781096055.84976481781098042.90676521781099588.2129331781102762.92132381781180914.85429571781261527.5583841781262166.32642581781262455.47228241781266268.58414151781354356.52117321781354995.9694651781355856.93816971781536853.31092361781703806.92632481781709239.78021071781709649.70756981781712321.38379761781718337.83594421781766440.45514631781770420.25207661781771329.15113041782058868.15240241782060757.39437151782114840.01730421782116626.14168121782118278.46144271782206800.38508151782208690.2186571782214986.56640481782215911.93149111782216753.3375161782217861.1438531782220507.59964751782221119.53614691782387963.94272471782391037.86762831782397174.43466951782400827.49992321782403418.57682781782407552.49232911782471685.10908841782486056.42131281782491119.7754541782492484.88775631782494767.62891581782495119.03816651782496927.16670061782502951.5276581782545134.69273971782631015.85844421782637542.97413681782647782.39912581782655816.30765841782665041.4627811782665991.96139241782667360.25157431782667581.47708061782737511.11845541782741219.0173351782808797.46143821782821919.54439121782822150.20869351782822770.9247681782827640.9542741782995600.16514921783076003.8864451783168083.37299471783169337.93949561783170232.14352231783171152.32074671783171554.32837651783174151.23807241783175503.07197071783176066.03923421783177692.68739181783179012.4248781783180240.505071783181436.92591291783278283.35669681783292257.8646151783517394.68319441783519695.6277161783678055.17990521783690220.13936731783712582.08824041783728419.41365361783729135.00310421783759453.8615671783761470.83648281783762862.82867961783766534.35036041783789595.08747321783930190.16791941783945570.6136721783954524.23125741783961828.6792711784028298.10868841784032600.80211161784036466.34077051784275841.48916361784366399.8074531784372627.88642331784374143.74483511784381186.08228351784389911.60351871784391089.69298031784392820.7430641784441094.15184241784454226.16887471784463132.38275381784464955.09228731784465761.79993341784466473.52550131784534975.79487631784608840.64008361784627943.05730491784637204.21883181784657189.48320531784657361.00413231784705158.30658131784715027.92925261784717991.8883981784733191.58883381784800377.45267271784812026.23560981784813186.95499831784814275.7592081784814470.29561351784818774.22292831784885821.62646681784901088.03214721784911607.10147881784916194.16596481784972617.54671784972803.68592141784973536.44179941784984817.86716681784986067.89907431784991877.94465471784998142.43659141785077031.5503931785084617.6083441785085343.4737631785086642.81846361785089769.95803481785137621.71359851785148601.48444631785172866.56524541785183014.3988751785218740.09921071785226243.70275641785228013.18298631785231361.67605071785240404.4484591785351675.21743441785496949.62526921785497755.40492131785677473.9457911785682237.71711851785766885.1947461785767243.61589121785772605.15616921785780183.1826661785782828.58324171785844265.65100531786268630.51386521786268947.91257071786269779.52634531786270697.67045071786277480.71831921786277724.14191751786281462.37932561786283154.36108371786283465.1903721786287166.76432871786289748.3343981786291875.15268561786293725.95022321786348241.80029181786352220.4185581786361747.21840071786366233.6671131786371555.18662381786372824.49700121786373802.18644671786377861.78411321786396710.64104411786403578.61991981786404487.37806181786404734.32843161786405449.91876441786436596.65412431786447073.79378181786447522.7193891786448405.54787921786449471.29032681786451444.1795711786452347.886241786452593.9396731786452926.5358991786456312.5733451786460743.97798971786463552.7407611786467845.41169861786474469.07857231786500206.03659271786562450.2208571786624610.81183391786686613.90171031786707650.41359851786728713.556651786729513.55272531786732847.73948051786775661.46056441786793299.49886561786793752.87393641786795187.76700571786795492.19988731786796902.2267191786864030.39654251786877454.05324031786878224.85780861786878707.51193381786880196.6659971786889576.6266361786896123.18303631786897808.01330881786959914.86220031786969834.2895571787034435.40246821787037132.78288671787041876.14791921787043670.97637461787052232.21001531787053261.528911787054828.71881531787057097.76064941787057270.45106361787080328.38963961787098382.79672071787212927.31802031787213906.47958251787391536.60465361787405049.95616221787413076.39776441787414062.51431941787415564.08572081787416411.7572251787418017.1897991787561357.46756271787562310.27913551787563996.93383121787565091.77498411787574301.54038881787588201.00754211787593104.40193721787642026.60537821787673561.52608231787674329.7224761787674711.51707051787677464.49858241787678337.43698141787681688.66121481787683130.10403851787687695.57127761787689287.68309451787690616.81437441787701810.4492311787703668.7168161787707222.34974881787708366.0833051787833483.0882821788012363.8917741788014226.56514121788014444.46203181788014831.32849721788027556.80003211788028811.14085361788029847.54328011788032884.15895151788037518.85965231788040681.6379951788076734.7216161788077227.50776861788077813.40856151788080193.68758751788083119.7505771788083817.84018371788095059.20750331788095390.66399481788097229.3971761788097679.40907861788098003.1718191788098465.73136541788102309.51320241788109877.86712551788112950.73934821788122140.83618191788172558.95067521788349675.4929531788421037.50810361788426780.8682451788428176.25174671788430564.5544511788430743.87801271788433679.29403541788435111.78655531788438488.66362931788446926.9636041788515909.69741731788516081.98195931788522742.14201621788618246.47165971788635186.9250721788635558.33422451788635789.4294821788636367.91405961788694058.5883521788695187.35930281788695679.84430241788695861.56550481788696174.07321571788696486.99952081788699610.1654271788706175.4002641788708791.35910771788768529.16503261788768847.25175241788769158.75513321788769956.4852821788856851.3099661788857231.0244051788864108.7148771788864607.17643431788870457.76393961788933503.53945111788933884.78966551788935130.28528831788935364.07300381788943599.84631631788963766.71460411788964690.96040131788994629.24461321788995358.84281789049116.39686181789049665.61125971789053429.83603051789053748.55635711789055270.97012281789057828.95167571789060636.77079321789117123.03734421789125148.24968121789129178.42693951789130927.8506781789146595.95088151789147921.85589051789149987.17254041789202919.9433471789239509.03819871789240837.55427121789242243.33970521789246296.43297581789251133.36522251789283294.90694831789298899.86048081789300585.6070341789324702.78062441789340077.6306211789398706.11733251789407509.91688921789498660.45305851789510669.77112251789510839.46737381789511619.68825751789581295.81189421789601219.44713191789739567.78348521789885723.33021471789885933.53306651789898352.98658251789905004.61845331789927305.62970261789936369.12114671789973334.6382191789992073.46435621789992512.85091611790001913.51180621790003970.81159541790004711.87018661790009748.20150661790010980.563481790042541.33307551790043246.03312541790044011.7960481790045365.07886651790045776.40539721790055634.55904561790056248.98219161790060735.58417271790063202.01464941790081459.97241231790082937.56307961790083490.03347731790084744.11084651790090112.46751881790100602.38838821790251685.559071790285748.06976461790285923.0950961790287196.05213281790307718.7959311790313539.11857491790318416.5424811790338813.12476831790339906.89636541790340205.84576921790348934.0324271790354642.9886841790356778.34959131790359760.3494821790361779.11455941790365161.28851681790403764.38521151790404822.77841931790406935.5803951790436526.46638541790488814.36095431790490416.58845541790490592.155171790492616.01131271790498171.64805581790504053.35340331790521353.2800581790522126.07214361790525340.22642921790533765.12383681790535505.65537071790549531.27069191790549787.546231790550871.74745061790552442.3069821790554260.66256551790576724.53606081790580253.77084781790597319.93879531790609658.3253711790678387.66511151790679391.60144541790683493.4025851790684819.89906951790685026.3671431790693709.90361431790698391.01632021790742067.88606551790750059.58717511790755282.54491231790755984.97108721790757072.51720711790757699.36573981790758011.12142591790832918.38622211790833628.34380031790836570.1869771790853896.1376691790861717.48689251790864015.2485381790865871.4113421790930662.4278971790930938.6585841790934191.4625721790947820.23689941791006632.2132861791104185.35599611791130144.40498351791180023.70980861791190422.93512341791194809.0171461791196869.236171791197896.1639331791203499.43079451791279736.86039731791292896.39418631791365933.61197641791390679.28499911791439538.93790341791478541.71099541791619004.99550531791641930.47821661791646342.44199751791650750.58091661791651538.68789551791652404.38552381791652656.8551831791712241.5331911791714077.8119021791714719.1824621791715115.86821131791715818.93575261791717070.63279871791719290.0486511791739049.2688761791806819.58328531792320095.47749421792324660.4223321792331567.27418541792334734.33781481792336099.6263131792343269.26501541792347633.77702141792348030.62554361792388630.0850941792389169.57291321792395942.16618731792418086.23562261792438008.4442681792492198.00541161792497253.00689581792535378.33694981792569620.993891792674349.83211111792837513.2094557
8dyc2trije5n3mqhzao5x8wtm5khfi8
User:Sam Sailor/test.js
2
98186
769912
753333
2026-10-24T09:06:06Z
Sam Sailor
26820
Test
769912
javascript
text/javascript
//<nowiki>
(function(mw, $) {
'use strict';
if (mw.config.get('wgNamespaceNumber') !== 0) return;
$(async function() {
// Check if page belongs to Category:Unisex given names or contains the template
const categories = mw.config.get('wgCategories') || [];
const isUnisexPage = categories.some(c => c.includes('Unisex given names')) || $('.mw-parser-output').text().includes('unisex given name');
if (!isUnisexPage) return;
// Collect all article titles linked in lists/bullet points on the page
const titleMap = new Map(); // title -> $element
const titles = [];
$('#mw-content-text li a[title]').each(function() {
const $a = $(this);
const title = $a.attr('title');
// Skip red links, help pages, or main namespaces other than 0
if ($a.hasClass('new') || title.includes(':')) return;
if (!titleMap.has(title)) {
titleMap.set(title, $a);
titles.push(title);
}
});
if (titles.length === 0) return;
const api = new mw.Api();
// Batch query titles in chunks of 50 to get their wikibase item (QID)
for (let i = 0; i < titles.length; i += 50) {
const chunk = titles.slice(i, i + 50);
try {
const data = await api.get({
action: 'query',
titles: chunk.join('|'),
prop: 'pageprops',
format: 'json'
});
const pages = data.query?.pages || {};
const qidMap = {};
const qidList = [];
Object.values(pages).forEach(p => {
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p.title;
}
});
if (!qidList.length) continue;
// Fetch P21 from Wikidata API
const wdRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qidList.join('|')}&props=claims&format=json&origin=*`);
const wdData = await wdRes.json();
const entities = wdData.entities || {};
for (const qid in entities) {
const pageTitle = qidMap[qid];
const $a = titleMap.get(pageTitle);
if (!$a) continue;
const claims = entities[qid].claims || {};
const p21 = claims.P21?.[0]?.mainsnak?.datavalue?.value?.id;
let symbol = ' ?';
let titleAttr = 'Gender: Unknown or ambiguous';
if (p21 === 'Q6581097') {
symbol = ' ♂';
titleAttr = 'Gender: Male';
} else if (p21 === 'Q6581072') {
symbol = ' ♀';
titleAttr = 'Gender: Female';
}
// Append symbol right after the link
$('<span style="font-size: 0.9em; margin-left: 4px; cursor: default;" title="' + titleAttr + '"></span>').text(symbol).insertAfter($a);
}
} catch (e) {
console.error('[UnisexGenderAppender] Error fetching batch:', e);
}
}
});
})(mediaWiki, jQuery);
(function(mw, $, OO) {
'use strict';
window.getterWindowManager = null;
window.GetterConfig = Object.assign({
enableSilentScan: true,
fontSize: '12px'
}, window.GetterConfig || {});
const IGNORE_SUFFIXES = ['Sr.', 'Sr', 'Jr.', 'Jr', 'II', 'III', 'IV', 'V', 'MD', 'PhD'];
const LANG_PREFS = ['frwiki', 'dewiki', 'eswiki', 'itwiki', 'nlwiki', 'svwiki'];
const NON_LANG_WIKIS = ['commonswiki', 'specieswiki', 'metawiki', 'mediawikiwiki', 'wikidatawiki', 'wikimaniawiki'];
const suffixRE = new RegExp('\\s(' + IGNORE_SUFFIXES.join('|').replace(/\./g, '\\.') + ')$', 'i');
const PREVIEW_ERROR_HTML = '<em style="color:#b32424">Preview unavailable</em>';
// --- Constructors ---
function GetterDialog(config) {
GetterDialog.super.call(this, config);
this.abortController = null;
this.api = new mw.Api();
this.fieldsInitPromise = null;
this.pageTextPromise = null;
this.cachedResults = null;
this.items = [];
this.previewCache = {};
this.previewElements = new Map();
this.previewPending = new Set();
this.previewGeneration = 0;
this.setupUI();
}
OO.inheritClass(GetterDialog, OO.ui.ProcessDialog);
GetterDialog.static.name = 'getterDialog';
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
// --- Static Definitions ---
GetterDialog.static.name = 'getterDialog';
GetterDialog.static.title = 'Getter: Wikipedia List Builder';
GetterDialog.static.actions = [{ action: 'generate', label: 'Generate List', flags: ['primary', 'progressive'], modes: 'edit' }, { action: 'back', label: 'Go back', modes: 'result' }, { action: 'exit', label: 'Exit', flags: 'safe', modes: ['edit', 'result'] }];
// --- GetterDialog Prototype Methods (Alphabetical) ---
GetterDialog.prototype.autoPopulateFields = async function() {
const title = mw.config.get('wgTitle'),
clean = this.cleanTitle(title);
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const given = text.match(/\{\{\s*given name\b[^}]*\}\}/i),
surname = /\{\{\s*surname\s*(?:\||\}\})/i.test(text);
if (surname || (given && /\|\s*type\s*=\s*both\b/i.test(given[0]))) this.surnameTags.addTag(clean);
if (given) this.givenTags.addTag(clean);
if (!surname && !given) this.givenTags.addTag(clean);
} catch (e) { this.givenTags.addTag(clean); }
};
GetterDialog.prototype.batchFetchPreviews = async function(items) {
const generation = this.previewGeneration;
for (let i = 0; i < items.length; i += 50) {
const chunk = items.slice(i, i + 50);
chunk.forEach(item => this.previewPending.add(item.page.title));
try {
const text = chunk.map(item => `<div data-getter-preview="${mw.html.escape(item.page.title)}">{{anbl|${item.page.title}}}</div>`).join('\n');
const data = await this.api.post({ action: 'parse', text: text, contentmodel: 'wikitext', prop: 'text' });
if (generation !== this.previewGeneration) return;
const $results = $(data.parse.text['*']).find('[data-getter-preview]');
$results.each((_, el) => {
const title = el.dataset.getterPreview;
$(el).find('a').attr('target', '_blank').attr('rel', 'noopener');
const html = el.innerHTML;
if (title && this.previewPending.has(title)) {
this.previewCache[title] = html;
const targetEl = this.previewElements.get(title);
if (targetEl) targetEl.innerHTML = html;
}
});
} catch (e) {
console.error('[Getter] Batch parse error:', e);
if (generation === this.previewGeneration) this.setPreviewError(chunk);
} finally {
chunk.forEach(item => this.previewPending.delete(item.page.title));
}
}
};
GetterDialog.prototype.cleanTitle = (t) => t.replace(suffixRE, '').replace(/\s?\([^)]*\)$/, '').trim();
GetterDialog.prototype.compileCriteria = (vals) => vals.map(v => v.trim()).filter(Boolean).map(v => new RegExp('^' + v.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\?/g, '.').replace(/\*/g, '.*') + '$', 'i'));
GetterDialog.prototype.copyToClipboard = async function(text) {
if (window.isSecureContext && navigator.clipboard?.writeText) {
try { await navigator.clipboard.writeText(text); return true; } catch { /* empty */ }
}
const $ta = $('<textarea>').val(text).css({ position: 'fixed', top: '-1000px', left: '-1000px' }).appendTo(document.body);
const el = $ta[0];
el.focus();
el.select();
const success = document.execCommand('copy');
$ta.remove();
return success;
};
GetterDialog.prototype.createButtonRow = function(colType) {
const $row = $('<div>').css({ display: 'flex', 'flex-wrap': 'wrap', gap: '5px', 'margin-top': '10px' });
const performCopy = async (shouldExit) => {
console.log('performCopy triggered for:', colType);
if (!this.level || !this.mode) {
mw.notify('Formatting widgets not ready yet.', { type: 'error' });
return;
}
const selectedItems = (colType === 'list') ? this.items.filter(i => i.status !== null) : this.items;
if (selectedItems.length === 0) {
mw.notify('No items to copy.', { type: 'warn' });
return;
}
try {
const level = this.level.getValue();
const mode = this.mode.getValue();
const getText = (i) => {
if (colType === 'list') return i.status === 'wikitext' ? i.wikitext : i.anbl;
if (colType === 'wikitext') return i.wikitext;
return i.anbl;
};
const formatted = this.formatListWithHeadings(selectedItems, level, mode, getText);
const success = await this.copyToClipboard(formatted);
if (success) {
mw.notify(`Copied ${selectedItems.length} items!`, { type: 'success' });
if (shouldExit) this.close();
}
} catch (e) {
console.error('Copy error:', e);
mw.notify('Failed to copy list.', { type: 'error' });
}
};
const copyBtn = new OO.ui.ButtonWidget({ label: 'Copy' });
copyBtn.on('click', () => performCopy(false));
$row.append(copyBtn.$element);
const exitBtn = new OO.ui.ButtonWidget({ label: 'Copy & Exit', flags: ['progressive'] });
exitBtn.on('click', () => performCopy(true));
$row.append(exitBtn.$element);
if (colType === 'wikitext' || colType === 'anbl') {
const toggleBtn = new OO.ui.ButtonWidget({ label: 'Toggle All' });
toggleBtn.on('click', () => {
const allSet = this.items.every(i => i.status === colType);
this.items.forEach(i => i.status = allSet ? null : colType);
this.refreshUI();
});
const smartBtn = new OO.ui.ButtonWidget({ label: 'Smart Add' });
smartBtn.on('click', () => {
this.items.forEach(i => { if (!i.status) i.status = colType; });
this.refreshUI();
});
$row.append(toggleBtn.$element, smartBtn.$element);
} else if (colType === 'list') {
const clearBtn = new OO.ui.ButtonWidget({ label: 'Clear', flags: ['destructive'] });
clearBtn.on('click', () => {
this.items.forEach(i => i.status = null);
this.refreshUI();
});
$row.append(clearBtn.$element);
}
return $row;
};
GetterDialog.prototype.ensureFieldsInitialized = function() {
if (!this.fieldsInitPromise) {
this.fieldsInitPromise = (async () => {
this.givenTags.clearItems();
this.middleTags.clearItems();
this.surnameTags.clearItems();
await this.autoPopulateFields();
})();
}
return this.fieldsInitPromise;
};
GetterDialog.prototype.fetchEntities = async function(qids, signal) {
const all = {};
for (let i = 0; i < qids.length; i += 50) {
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');
const res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qids.slice(i, i + 50).join('|')}&props=claims|labels|descriptions|sitelinks&languages=en&format=json&origin=*`, { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
Object.assign(all, data.entities || {});
}
return all;
};
GetterDialog.prototype.findCandidates = async function(bestAnchor, compiled, excludeList, existing, signal) {
const resultsMap = new Map();
const term = bestAnchor.term.replace(/"/g, '\\"').trim();
const isWildcard = /[*?]/.test(term);
const DEBUG = true;
let searches = [];
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
searches.push(`intitle:${term}`);
} else if (isWildcard) {
searches = [`intitle:${term.replace(/\?/g, '*')}`, `intitle:"${term.replace(/[*?]/g, '')}"`];
} else {
searches = [`intitle:"${term}"`];
}
const MAX_CONTINUATIONS = 20;
const runSearch = async (srsearch) => {
let cont = {};
let continuations = 0;
let fetched = 0;
try {
do {
const data = await this.api.get({
action: 'query',
list: 'search',
srsearch,
srlimit: 'max',
sroffset: cont.sroffset || 0,
format: 'json'
}, { signal });
const results = data.query?.search || [];
const aarons = results.filter(r => r.title.startsWith('Aaron')).map(r => r.title);
if (aarons.length) {
console.log(`[${srsearch}] offset=${cont.sroffset || 0}`, aarons);
}
for (const p of results) {
const title = p.title.replace(/_/g, ' ').trim();
if (title.startsWith('Aaron')) {
console.log('Checking', title);
console.log({
existing: existing.has(title),
alreadyFound: resultsMap.has(title),
excluded: excludeList.some(e => title.toLowerCase().includes(e)),
matches: this.matchesCriteria(title, compiled)
});
}
if (existing.has(title) || resultsMap.has(title)) continue;
if (excludeList.some(e => title.toLowerCase().includes(e))) continue;
if (this.matchesCriteria(title, compiled)) {
resultsMap.set(title, {
title,
pageid: p.pageid
});
}
if (title.includes('Rahsaan')) {
console.log('Found Rahsaan', title);
}
}
cont = data.continue || {};
continuations++;
} while (cont.sroffset && continuations < MAX_CONTINUATIONS && !signal?.aborted);
} catch (e) {
if (e.name === 'AbortError') {
throw e;
}
console.error(`[Getter] Search error for "${srsearch}":`, e);
}
if (DEBUG) {
console.log(`[Getter] "${srsearch}" → ${fetched} hits, ${resultsMap.size} candidates`);
}
};
for (const srsearch of searches) {
if (signal?.aborted) {
break;
}
await runSearch(srsearch);
}
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
if (DEBUG) {
console.log(`[Getter] Starting A–Z boost for "${term}"`);
}
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (const letter of alphabet) {
if (signal?.aborted) {
break;
}
await runSearch(`intitle:${letter} intitle:${term}`);
}
}
if (DEBUG) {
console.log(`[Getter] Final candidates for "${term}": ${resultsMap.size}`);
}
return resultsMap;
};
GetterDialog.prototype.formatListWithHeadings = function(items, level, mode, getText) {
if (mode === 'none') return items.map(getText).join('\n');
if (mode === 'smart' && items.length > 0) {
const firstChar = items[0].sortKey.charAt(0).toUpperCase();
if (items.every(i => i.sortKey.charAt(0).toUpperCase() === firstChar)) {
return items.map(getText).join('\n');
}
}
const prefix = '='.repeat(level);
let segments = [];
if (mode === 'alpha') {
const groups = {};
items.forEach(i => {
const char = i.sortKey.charAt(0).toUpperCase();
if (!groups[char]) groups[char] = [];
groups[char].push(i);
});
Object.keys(groups).sort().forEach(char => segments.push({ title: char, items: groups[char] }));
} else {
const count = items.length;
const num = count > 200 ? 26 : (count > 150 ? 3 : 2);
const chunkSize = Math.floor(count / num);
let start = 0;
for (let i = 0; i < num && start < count; i++) {
let end = (i === num - 1) ? count : start + chunkSize;
while (end < count && items[end - 1].sortKey.charAt(0) === items[end].sortKey.charAt(0)) end++;
const chunk = items.slice(start, end);
if (chunk.length) {
const title = this.getSmartHeading(chunk[0], chunk[chunk.length - 1]);
segments.push({ title, items: chunk });
}
start = end;
}
}
return segments.map(s => `${prefix} ${s.title} ${prefix}\n${s.items.map(getText).join('\n')}`).join('\n\n');
};
GetterDialog.prototype.formatResult = function(p, mode, includeIll) {
if (mode === 'anbl') return `* {{anbl|${p.title}}}`;
const bY = p.ent?.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const dY = p.ent?.claims?.P570?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const bio = bY ? (dY ? ` (${bY}–${dY})` : ` (born ${bY})`) : '';
const rawDesc = (p.desc || '').replace(/\s?\(\s*(?:born|d\.)?\s*\d{4}(?:\s*–\s*\d{4})?\s*\)$/i, '').trim();
const suffix = rawDesc ? `, ${rawDesc}` : '';
let display = `[[${p.title}]]`;
if (includeIll) {
const ill = this.getPreferredSitelink(p.ent);
if (ill) display = `{{ill|${p.title}|${ill.lang}|${ill.title}}}`;
}
return `* ${display}${bio}${suffix}`;
};
GetterDialog.prototype.getActionProcess = function(action) {
if (action === 'generate') return new OO.ui.Process(async () => { await this.runGetter(); });
if (action === 'back') {
if (this.abortController) this.abortController.abort();
return new OO.ui.Process(() => {
this.resultContainer.$element.hide();
this.fieldset.$element.show();
this.actions.setMode('edit');
});
}
if (action === 'exit') { if (this.abortController) this.abortController.abort(); return new OO.ui.Process(() => this.close()); }
return GetterDialog.super.prototype.getActionProcess.call(this, action);
};
GetterDialog.prototype.getBestAnchor = function() {
const getValues = (w) => w.getValue().map(t => t.trim()).filter(Boolean);
const candidates = [...getValues(this.surnameTags).map(t => ({ term: t, field: 'surname', weight: 2000 + this.score(t) })), ...getValues(this.givenTags).map(t => ({ term: t, field: 'given', weight: 1000 + this.score(t) })), ...getValues(this.middleTags).map(t => ({ term: t, field: 'middle', weight: 0 + this.score(t) }))];
return candidates.length === 0 ? null : candidates.reduce((b, c) => (c.weight > b.weight ? c : b));
};
GetterDialog.prototype.getCompiledCriteria = function() { return { given: this.compileCriteria(this.givenTags.getValue()), surname: this.compileCriteria(this.surnameTags.getValue()), middle: this.compileCriteria(this.middleTags.getValue()) }; };
GetterDialog.prototype.getExistingLinks = async function() {
const titles = new Set();
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const regexes = [/\[\[([^\]|#]+)/g, /\{\{(?:anbl|Annotated biography link)\|([^}|]+)/gi];
regexes.forEach(re => { let m; while ((m = re.exec(text)) !== null) titles.add(this.normalizeTitle(m[1])); });
} catch (e) { console.error('[Getter] Parse error:', e); }
return titles;
};
GetterDialog.prototype.getPageText = function() { return this.pageTextPromise ??= this.api.get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'wikitext', format: 'json' }); };
GetterDialog.prototype.getPreferredSitelink = function(ent) {
const sitelinks = ent.sitelinks || {};
const key = LANG_PREFS.find(l => sitelinks[l]) || Object.keys(sitelinks).find(k => k.endsWith('wiki') && k !== 'enwiki' && !NON_LANG_WIKIS.includes(k));
return key ? { lang: key.replace(/wiki$/, ''), title: sitelinks[key].title } : null;
};
GetterDialog.prototype.getSetupProcess = function(data) {
return GetterDialog.super.prototype.getSetupProcess.call(this, data).next(() => {
if (data && data.pageTitle) {
if (this.givenTags.getValue().indexOf(data.pageTitle) === -1) {
this.givenTags.addTag(data.pageTitle);
}
}
this.autoPopulateFields();
});
};
GetterDialog.prototype.getSmartHeading = function(startItem, endItem) {
const char1 = startItem.sortKey.charAt(0).toUpperCase();
const char2 = endItem.sortKey.charAt(0).toUpperCase();
return char1 === char2 ? char1 : `${char1}–${char2}`;
};
GetterDialog.prototype.getSortKey = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return parts.length <= 1 ? clean.toLowerCase() : (parts[parts.length - 1] + ', ' + parts.slice(0, -1).join(' ') + ' | ' + clean).toLowerCase();
};
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
GetterDialog.prototype.matchesCriteria = function(title, compiled) {
const parts = this.tokenizeTitle(title);
const check = (val, regs) => regs.length === 0 || regs.some(re => re.test(val));
let givenMatch = check(parts.given, compiled.given);
if (!givenMatch && compiled.given.length > 0) {
const allTokens = this.cleanTitle(title).toLowerCase().split(/\s+/);
givenMatch = compiled.given.some(re => allTokens.some(token => re.test(token)));
}
const surnameMatch = check(parts.surname, compiled.surname);
const middleMatch = compiled.middle.length === 0 || compiled.middle.some(re => parts.middle.some(t => re.test(t)));
if (title.startsWith('Aaron')) {
console.log({
title,
parts,
givenMatch,
surnameMatch,
middleMatch,
compiled
});
}
return givenMatch && surnameMatch && middleMatch;
};
GetterDialog.prototype.normalizeTitle = (t) => { const title = mw.Title.newFromText(t); return title ? title.getPrefixedText() : t.replace(/_/g, ' ').trim(); };
GetterDialog.prototype.processEntityBatch = async function(pageIds, signal) {
const confirmed = new Map();
const pD = await this.api.get({ action: 'query', prop: 'pageprops|description', pageids: pageIds.join('|'), format: 'json' }, { signal });
const qidMap = {},
qidList = [];
Object.values(pD.query.pages).forEach(p => {
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p;
}
});
if (!qidList.length) return confirmed;
const entities = await this.fetchEntities(qidList, signal);
for (const qid in entities) {
const ent = entities[qid],
p = qidMap[qid],
isH = ent.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
if ((this.humanOnly.isSelected() && !isH) || (this.excludeHumans.isSelected() && isH)) continue;
confirmed.set(p.title, { title: p.title, desc: p.description || ent.descriptions?.en?.value || '', ent });
}
return confirmed;
};
GetterDialog.prototype.refreshPreviews = function() {
this.previewCache = {};
this.previewPending.clear();
this.previewGeneration++;
this.refreshUI();
};
GetterDialog.prototype.refreshUI = function() {
this.col1.empty();
this.col2.empty();
this.col3.empty();
this.col4.empty();
this.previewElements.clear();
const $ul = $('<ul>').css({ 'list-style-type': 'disc', 'padding-left': '20px', margin: '0' });
this.col4.append($ul);
const fSize = window.GetterConfig.fontSize;
const itemStyle = {
cursor: 'pointer',
padding: '2px',
'font-size': fSize,
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
};
this.items.forEach(item => {
const $w = $('<div>').text(item.wikitext).attr('title', item.wikitext).css({ ...itemStyle, opacity: item.status === 'wikitext' ? 0.3 : 1 });
$w.on('click', () => {
item.status = (item.status === 'wikitext' ? null : 'wikitext');
this.refreshUI();
});
this.col1.append($w);
const $a = $('<div>').text(item.anbl).attr('title', item.anbl).css({ ...itemStyle, opacity: item.status === 'anbl' ? 0.3 : 1 });
$a.on('click', () => {
item.status = (item.status === 'anbl' ? null : 'anbl');
this.refreshUI();
});
this.col3.append($a);
const $li = $('<li>').attr('title', item.page.title).css({
'font-size': fSize,
'margin-bottom': '2px',
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
}).html(this.previewCache[item.page.title] || '<em>Loading...</em>');
$ul.append($li);
this.previewElements.set(item.page.title, $li[0]);
});
this.items.filter(i => i.status).forEach(item => {
const text = item.status === 'wikitext' ? item.wikitext : item.anbl;
this.col2.append($('<div>').text(text).attr('title', text).css({ 'margin-bottom': '4px', 'font-size': fSize }));
});
const missing = this.items.filter(i => !(i.page.title in this.previewCache) && !this.previewPending.has(i.page.title));
if (missing.length) this.batchFetchPreviews(missing);
};
GetterDialog.prototype.renderResults = async function(confirmed) {
this.previewGeneration++;
this.previewPending.clear();
this.items = Array.from(confirmed.values()).map(p => ({
page: p,
sortKey: this.sortBySurname.isSelected() ? this.getSortKey(p.title) : this.cleanTitle(p.title).toLowerCase(),
wikitext: this.formatResult(p, 'wiki', this.includeIll.isSelected()),
anbl: this.formatResult(p, 'anbl', this.includeIll.isSelected()),
status: null
})).sort((a, b) => a.sortKey.localeCompare(b.sortKey, undefined, { sensitivity: 'base' }));
this.refreshUI();
this.fieldset.$element.hide();
this.resultContainer.$element.show();
this.resultControls.$element.show();
this.actions.setMode('result');
};
GetterDialog.prototype.runGetter = async function() {
this.abortController = new AbortController();
this.cachedResults = null;
try {
const best = this.getBestAnchor();
if (!best) { mw.notify('Please enter search terms.', { type: 'error' }); return; }
const existing = this.updateMode.isSelected() ? await this.getExistingLinks() : new Set();
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), this.excludeTags.getValue().map(e => e.trim().toLowerCase()), existing, this.abortController.signal);
const confirmed = new Map(),
pageIds = Array.from(resultsMap.values()).map(p => p.pageid);
for (let i = 0; i < pageIds.length; i += 50) {
const batchConfirmed = await this.processEntityBatch(pageIds.slice(i, i + 50), this.abortController.signal);
batchConfirmed.forEach((val, key) => confirmed.set(key, val));
}
this.cachedResults = confirmed;
this.renderResults(confirmed);
} catch (e) { if (e.name !== 'AbortError') mw.notify('Error generating list.', { type: 'error' }); } finally { this.abortController = null; }
};
GetterDialog.prototype.score = (term) => { const w = (term.match(/[*?]/g) || []).length; return (w === 0 ? 1000 : 0) + term.replace(/[*?]/g, '').length - w; };
GetterDialog.prototype.setPreviewError = function(items) {
items.forEach(item => {
this.previewCache[item.page.title] ??= PREVIEW_ERROR_HTML;
const targetEl = this.previewElements.get(item.page.title);
if (targetEl) targetEl.innerHTML = this.previewCache[item.page.title];
});
};
GetterDialog.prototype.setupUI = function() {
this.givenTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Given names...', allowArbitrary: true });
this.middleTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Middle terms...', allowArbitrary: true });
this.surnameTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Surnames...', allowArbitrary: true });
this.excludeTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Exclude strings...', allowArbitrary: true });
this.humanOnly = new OO.ui.CheckboxInputWidget({ selected: true });
this.excludeHumans = new OO.ui.CheckboxInputWidget({ selected: false });
this.includeIll = new OO.ui.CheckboxInputWidget({ selected: false });
this.sortBySurname = new OO.ui.CheckboxInputWidget({ selected: true });
this.updateMode = new OO.ui.CheckboxInputWidget({ selected: false });
this.mode = new OO.ui.RadioSelectInputWidget({
options: [{ data: 'none', label: 'No headings' }, { data: 'smart', label: 'Smart grouping' }, { data: 'alpha', label: 'A-Z' }],
value: 'none'
});
this.level = new OO.ui.RadioSelectInputWidget({
options: [{ data: 2, label: 'Level 2' }, { data: 3, label: 'Level 3' }, { data: 4, label: 'Level 4' }],
value: 3
});
if (!this.level || !this.mode) {
console.error('Level or mode widget missing during UI setup');
}
mw.util.addCSS(` .getter-result-fieldset .oo-ui-fieldsetLayout-header {
font-size: 13px !important;
color: #444;
}
.getter-formatting-container {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 40px;
}
.getter-formatting-container .oo-ui-fieldLayout {
display: flex;
align-items: center;
margin-top: 2px;
}
.getter-formatting-container .oo-ui-fieldLayout-label {
margin-right: 12px;
font-weight: bold;
width: auto;
flex-shrink: 0;
line-height: 1.4;
}
.oo-ui-radioSelectInputWidget {
vertical-align: middle;
}
`);
this.resultControls = new OO.ui.FieldsetLayout({ label: 'Output Formatting', classes: ['getter-result-fieldset'] });
const $formattingRow = $('<div>').addClass('getter-formatting-container');
const modeLayout = new OO.ui.FieldLayout(this.mode, {
label: 'Grouping Mode',
align: 'top'
});
const levelLayout = new OO.ui.FieldLayout(this.level, {
label: 'Heading Level',
align: 'top'
});
$formattingRow.append(modeLayout.$element, levelLayout.$element);
this.resultControls.$element.append($formattingRow);
this.fieldset = new OO.ui.FieldsetLayout({ label: 'Advanced Filter Settings' });
this.fieldset.addItems([
new OO.ui.FieldLayout(this.givenTags, { label: 'Given names', align: 'top' }),
new OO.ui.FieldLayout(this.middleTags, { label: 'Middle terms', align: 'top' }),
new OO.ui.FieldLayout(this.surnameTags, { label: 'Surnames', align: 'top' }),
new OO.ui.FieldLayout(this.excludeTags, { label: 'Exclude strings', align: 'top' }),
new OO.ui.FieldLayout(this.humanOnly, { label: 'Humans only', align: 'inline' }),
new OO.ui.FieldLayout(this.excludeHumans, { label: 'Exclude humans', align: 'inline' }),
new OO.ui.FieldLayout(this.sortBySurname, { label: 'Sort by surname', align: 'inline' }),
new OO.ui.FieldLayout(this.updateMode, { label: 'Exclude existing links', align: 'inline' }),
new OO.ui.FieldLayout(this.includeIll, { label: 'Include {{ill}}', align: 'inline' })
]);
const colStyle = { height: '480px', 'overflow-y': 'auto', 'overflow-x': 'hidden', border: '1px solid #ccc', padding: '10px', 'white-space': 'nowrap', 'text-overflow': 'ellipsis' };
this.col1 = $('<div>').css(colStyle);
this.col2 = $('<div>').css({ ...colStyle, background: '#f9f9f9' });
this.col3 = $('<div>').css(colStyle);
this.col4 = $('<div>').css(colStyle);
const columns = [this.col1, this.col2, this.col3, this.col4];
columns.forEach(col => {
col.on('scroll', (e) => {
const scrollTop = e.target.scrollTop;
columns.forEach(otherCol => { if (otherCol[0] !== e.target && otherCol[0].scrollTop !== scrollTop) otherCol[0].scrollTop = scrollTop; });
});
});
const $refreshBtn = new OO.ui.ButtonWidget({ label: 'Refresh', icon: 'reload', flags: ['progressive'] });
$refreshBtn.on('click', () => this.refreshPreviews());
this.resultContainer = new OO.ui.PanelLayout({ padded: true, expanded: true });
const $columns = $('<div>').css({ display: 'flex', gap: '10px' });
const createHeader = (label) => new OO.ui.LabelWidget({ label: label }).$element.css({ 'font-weight': 'bold', 'font-size': '13px', 'margin-bottom': '5px' });
$columns.append($('<div>').css('flex', '1.2').append(createHeader('Wikitext'), this.col1, this.createButtonRow('wikitext')), $('<div>').css('flex', '1.2').append(createHeader('Chosen List'), this.col2, this.createButtonRow('list')), $('<div>').css('flex', '0.8').append(createHeader('Anbl'), this.col3, this.createButtonRow('anbl')), $('<div>').css('flex', '1.2').append(createHeader('Anbl Preview'), this.col4, $('<div>').css('margin-top', '10px').append($refreshBtn.$element)));
this.resultContainer.$element.append($columns, $('<div>').css('margin-top', '20px').append(this.resultControls.$element)).hide();
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.panel.$element.append(this.fieldset.$element, this.resultContainer.$element);
};
GetterDialog.prototype.silentScan = async function() {
if (window.GetterConfig.enableSilentScan === false || !/(?:list of|surname|given name)/i.test(mw.config.get('wgTitle'))) return;
$('.mw-notification[data-notification-tag="getter-discovery"]').remove();
await this.ensureFieldsInitialized();
const best = this.getBestAnchor();
if (!best) return;
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), [], await this.getExistingLinks(), null);
if (!resultsMap.size) return;
const confirmed = await this.processEntityBatch(Array.from(resultsMap.values()).map(p => p.pageid).slice(0, 50), null);
if (confirmed.size > 0) {
this.cachedResults = confirmed;
const $link = $('<a>', { text: 'Review', href: '#', style: 'font-weight:bold; cursor:pointer' });
$link.on('click', (e) => {
e.preventDefault();
$link.closest('.mw-notification').remove();
this.windowManager.openWindow(this);
this.renderResults(this.cachedResults);
});
mw.notify($('<span>').text(`Getter found ${resultsMap.size > 50 ? '50+' : confirmed.size} potential entries. `).append($link), { tag: 'getter-discovery', autoHide: false });
}
};
GetterDialog.prototype.tokenizeTitle = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return {
given: parts[0] || '',
surname: parts.length > 1 ? parts[parts.length - 1] : '',
middle: parts.length > 2 ? parts.slice(1, -1) : []
};
};
// --- Initialization ---
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui', 'oojs-ui-widgets', 'oojs-ui-windows']).then(() => {
const wm = new OO.ui.WindowManager();
$(document.body).append(wm.$element);
const dialog = new GetterDialog({ size: 'full' });
dialog.windowManager = wm;
wm.addWindows([dialog]);
$(mw.util.addPortletLink('p-tb', '#', 'Getter', 't-getter')).on('click', (e) => {
e.preventDefault();
wm.openWindow(dialog);
});
});
}(mediaWiki, jQuery, OO));
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "0.5",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 10,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
/* global ComradeLib, mw, $ */
(async function() {
try {
await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-options.js&action=raw&ctype=text/javascript');
} catch (e) {
console.warn("Comrade: Could not load /comrade-options.js, using fallback defaults.", e);
}
window.ComradeOptions = Object.assign({
version: "0.5.0",
excludedTemplates: [],
nudgeTimeout: 10000,
debugMode: false
}, window.ComradeOptions || {});
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 res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qid}&props=claims|descriptions|labels&languages=en&format=json&origin=*`).then(r => r.json());
return res.entities[qid];
}
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;
ComradeLib.checkLeadCapitalization(currentTitle);
// 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);
await ComradeLib.checkNoShortDesc(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 isRedirect = !!mw.config.get("wgIsRedirect");
const userExclusions = window.comradeQualityExclusions || [];
const allExclusions = [...window.ComradeOptions.excludedTemplates, ...userExclusions];
const exclusionRegexString = allExclusions.map(t => t.replace("Template:", "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
const exclusionRegex = new RegExp(exclusionRegexString, 'i');
if (isRedirect || exclusionRegex.test(wikitext)) {
console.log("Comrade check skipped: Page is a redirect or excluded via options.");
return;
}
ComradeLib.checkQualityConsistency();
} catch (err) {
console.error("Comrade error:", err);
}
}
init();
})();
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Search') {
return;
}
mw.loader.using(['mediawiki.util']).then(function() {
$(function() {
const $resultsInfo = $('.results-info');
const totalResultsRaw = $resultsInfo.attr('data-mw-num-results-total') || '0';
function formatWithCommas(numberString) {
return numberString.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
const state = {
language: mw.config.get('wgContentLanguage'),
totalResultsFormatted: formatWithCommas(totalResultsRaw)
};
let titlesCache = null;
function extractResultTitles() {
const currentResults = $('.mw-search-result-heading');
if (!titlesCache || titlesCache.length !== currentResults.length) {
titlesCache = currentResults.map(function() {
return $(this).find('a:first').attr('title');
}).get().filter(Boolean);
}
return titlesCache;
}
mw.hook("wikipage.content").add(function() {
titlesCache = null;
});
function addLimitOptions() {
const selects = document.querySelectorAll('.cdx-table-pager__limit-form select[name="limit"]');
if (!selects.length) return;
const valuesToAdd = ["1000", "2500", "5000"];
selects.forEach(function(select) {
valuesToAdd.forEach(function(value) {
if (select.querySelector('option[value="' + value + '"]')) return;
const option = document.createElement("option");
option.value = value;
option.textContent = formatWithCommas(value) + " items";
const existingOptions = Array.from(select.querySelectorAll("option"));
const insertBefore = existingOptions.find(function(opt) {
return Number(opt.value) > Number(value);
});
if (insertBefore) {
select.insertBefore(option, insertBefore);
} else {
select.appendChild(option);
}
});
});
}
mw.hook("wikipage.content").add(addLimitOptions);
const numLinks = $('.mw-numlink');
if (numLinks.length > 4 && !numLinks.eq(4).next().is('a[href*="limit=5000"]')) {
const $firstAnchor = numLinks.eq(4);
const $firstSpan = $('<span> | </span>').insertAfter($firstAnchor);
const url1 = new URL(window.location.href);
url1.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url1.toString()
}).insertAfter($firstAnchor).before($firstSpan);
}
if (numLinks.length > 9 && !numLinks.eq(9).next().is('a[href*="limit=5000"]')) {
const $secondAnchor = numLinks.eq(9);
const $secondSpan = $('<span> | </span>').insertAfter($secondAnchor);
const url2 = new URL(window.location.href);
url2.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url2.toString()
}).insertAfter($secondAnchor).before($secondSpan);
}
const $container = $("<div></div>").addClass("enhanced-search-container").hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const interwikiResults = document.getElementById("mw-interwiki-results");
if (interwikiResults) {
$container.addClass("enhanced-search-floating").insertBefore("#mw-interwiki-results");
} else {
$container.addClass("enhanced-search-absolute").insertBefore(".searchresults");
}
const totalFormattedStr = state.totalResultsFormatted;
const $titleElement = $("<div>Show a shortened list of<br />this page's " + $('.mw-search-result-heading').length + " results <small>(of total of " + totalFormattedStr + ")</small></div>").addClass("enhanced-header").appendTo($container);
const $contentArea = $("<div></div>").addClass("enhanced-content-area").appendTo($container).hide();
const $note1 = $("<div>Copy this page\'s " + $('.mw-search-result-heading').length + " results to the clipboard as...</div>").addClass("enhanced-note").insertAfter($container).hide();
function setupHoverTooltip(element, message) {
$(element).attr('title', message).hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
}
const $note2 = $("<div>...plain text<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note1).hide();
setupHoverTooltip($note2, 'This button copies this page\'s results to the clipboard as plain text, i.e. without wikitext markup. Since this copies only the results shown on this search page, make sure you have clicked the "view 20, 50, 100, 250, 500, 5000" link. This format is compatible with PetScan and AutoWikiBrowser (AWB) for example.');
const $note3 = $("<div>...wikitext<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note2).hide();
setupHoverTooltip($note3, 'This button copies this page\'s results to the clipboard and also adds wikitext markup.');
const $note4 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note3).hide();
setupHoverTooltip($note4, 'This button copies a PetScan URL with this page\'s results to the clipboard. Note that if the PetScan URL becomes too long (~250+ pages), the URL might not work.');
const $note5 = $("<div>...or this search query's total results of " + totalFormattedStr + "<sup> (?)</sup> as...</div>").addClass("enhanced-note").insertAfter($note4).hide().attr('title', 'Note that the actual limit is 10,000, which is MediaWiki\'s maximum limit.').hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const $note6 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note5).hide();
setupHoverTooltip($note6, 'This button copies a PetScan URL with all ' + totalFormattedStr + ' results to the clipboard. Note that the maximum limit is 10,000 due to MediaWiki\'s restriction.');
const $note7 = $("<div><sup>(?)</sup> = hover over for tooltip details</div>").addClass("enhanced-note-right").insertAfter($note6).hide();
const noteElements = [$note1, $note2, $note3, $note4, $note5, $note6, $note7];
if (interwikiResults !== null) {
noteElements.forEach($el => $el.addClass("enhanced-floating-elements"));
} else {
noteElements.forEach(function($el, index) {
$el.addClass("enhanced-absolute-elements enhanced-position-" + index);
});
}
function formatPlain() {
return extractResultTitles().join("\r\n");
}
function formatWikitext() {
const titles = extractResultTitles().map(title => "* [[" + title + "]]");
const header = "This is a list of [" + window.location.href + " these search results] (made with the script [[User:JAAqqO/EnhancedSearchPage|EnhancedSearchPage]]):\r\n";
return header + titles.join("\r\n");
}
function formatPetScan() {
const encodedTitles = extractResultTitles().map(encodeURIComponent).join("%0A");
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&manual_list=" + encodedTitles + "&manual_list_wiki=" + state.language + "wiki&project=wikipedia&language=" + state.language + "&doit=";
}
function formatPetScanTotal() {
const oouiInput = document.getElementById("ooui-php-1");
const value = oouiInput ? oouiInput.value : "";
if (value) {
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&ns%5B0%5D=1&search_query=" + encodeURIComponent(value) + "&search_wiki=" + state.language + "wiki&search_max_results=10000&project=wikipedia&language=" + state.language + "&doit=";
}
return "";
}
function copyToClipboard(text) {
if (!text) return;
if (!navigator.clipboard) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
} catch (e) {
console.error('Fallback copy failed', e);
}
textarea.remove();
} else {
navigator.clipboard.writeText(text).catch(function(err) {
console.error("Copy failed: ", err);
});
}
}
function showCopiedMessage(event) {
const posX = event.pageX;
const posY = event.pageY;
const $msg = $("<div class='copied-message'>Copied!</div>").appendTo("body");
$msg.css({
position: "absolute",
top: (posY + 10) + "px",
left: (posX + 10) + "px",
backgroundColor: "#f0f0f0",
border: "1px solid grey",
padding: "5px 10px",
borderRadius: "5px",
zIndex: 9999
});
setTimeout(function() {
$msg.fadeOut("slow", function() {
$(this).remove();
});
}, 500);
}
function performButtonAnimation($element) {
$element.css("transform", "scaleY(0.9)").animate({
opacity: '0.5'
}, "fast", function() {
$(this).animate({
opacity: '1'
}, "slow").css("transform", "scaleY(1)");
});
}
let isExpanded = false;
function showContentArea() {
if (isExpanded) return;
isExpanded = true;
const currentResults = $('.mw-search-result-heading');
$contentArea.empty().append(currentResults.clone());
$contentArea.slideDown();
$(".searchalttitle").remove();
$container.addClass("enhanced-active-container");
$titleElement.remove();
noteElements.forEach($el => $el.show());
}
function bindCopyAction($element, formatterFn) {
$element.click(function(event) {
copyToClipboard(formatterFn());
showCopiedMessage(event);
performButtonAnimation($(this));
});
}
$container.click(showContentArea);
bindCopyAction($note2, formatPlain);
bindCopyAction($note3, formatWikitext);
bindCopyAction($note4, formatPetScan);
bindCopyAction($note6, formatPetScanTotal);
const $pageDown = $("<div><br /><br />[ ↓ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the bottom of the page');
}, function() {
$(this).removeClass("hover");
}).insertBefore('.mw-search-result:eq(0)');
const $pageUp = $("<div>[ ↑ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the top of the page');
}, function() {
$(this).removeClass("hover");
}).insertAfter('.mw-search-result:eq(-1)');
$pageDown.click(function(event) {
event.preventDefault();
$('html, body').animate({
scrollTop: $(document).height()
}, 500);
});
$pageUp.click(function() {
$('html, body').animate({
scrollTop: 0
}, 500);
});
$("<span> (<b>" + $('.mw-search-result-heading').length + "</b> shown on the page)</span>").appendTo('.results-info');
const searchDidYouMean = document.querySelector('.searchdidyoumean');
if (searchDidYouMean) {
searchDidYouMean.style.color = '#b32424';
searchDidYouMean.style.fontSize = '20px';
}
});
});
})();
//</nowiki>
c07o69og98r4bpd1p9d2yelfqxga3aj
769913
769912
2026-10-24T09:13:13Z
Sam Sailor
26820
Test
769913
javascript
text/javascript
//<nowiki>
(function(mw, $) {
'use strict';
if (mw.config.get('wgNamespaceNumber') !== 0) return;
$(async function() {
// Check if page belongs to Category:Unisex given names or contains the template
const categories = mw.config.get('wgCategories') || [];
const isUnisexPage = categories.some(c => c.includes('Unisex given names')) || $('.mw-parser-output').text().includes('unisex given name');
if (!isUnisexPage) return;
// Collect all article titles linked in lists/bullet points on the page
const titleMap = new Map(); // title -> $element
const titles = [];
$('#mw-content-text li a[title]').each(function() {
const $a = $(this);
const title = $a.attr('title');
// Skip red links, help pages, or main namespaces other than 0
if ($a.hasClass('new') || title.includes(':')) return;
if (!titleMap.has(title)) {
titleMap.set(title, $a);
titles.push(title);
}
});
if (titles.length === 0) return;
const api = new mw.Api();
// Batch query titles in chunks of 50 to get their wikibase item (QID)
for (let i = 0; i < titles.length; i += 50) {
const chunk = titles.slice(i, i + 50);
try {
const data = await api.get({
action: 'query',
titles: chunk.join('|'),
prop: 'pageprops',
redirects: 1,
format: 'json'
});
const pages = data.query?.pages || {};
const qidMap = {};
const qidList = [];
Object.values(pages).forEach(p => {
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p.title;
}
});
if (!qidList.length) continue;
// Fetch P21 from Wikidata API
const wdRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qidList.join('|')}&props=claims&format=json&origin=*`);
const wdData = await wdRes.json();
const entities = wdData.entities || {};
for (const qid in entities) {
const pageTitle = qidMap[qid];
const $a = titleMap.get(pageTitle);
if (!$a) continue;
const claims = entities[qid].claims || {};
const p21 = claims.P21?.[0]?.mainsnak?.datavalue?.value?.id;
let symbol = ' ?';
let titleAttr = 'Gender: Unknown or ambiguous';
if (p21 === 'Q6581097') {
symbol = ' ♂';
titleAttr = 'Gender: Male';
} else if (p21 === 'Q6581072') {
symbol = ' ♀';
titleAttr = 'Gender: Female';
}
// Append symbol right after the link
$('<span style="font-size: 0.9em; margin-left: 4px; cursor: default;" title="' + titleAttr + '"></span>').text(symbol).insertAfter($a);
}
} catch (e) {
console.error('[UnisexGenderAppender] Error fetching batch:', e);
}
}
});
})(mediaWiki, jQuery);
(function(mw, $, OO) {
'use strict';
window.getterWindowManager = null;
window.GetterConfig = Object.assign({
enableSilentScan: true,
fontSize: '12px'
}, window.GetterConfig || {});
const IGNORE_SUFFIXES = ['Sr.', 'Sr', 'Jr.', 'Jr', 'II', 'III', 'IV', 'V', 'MD', 'PhD'];
const LANG_PREFS = ['frwiki', 'dewiki', 'eswiki', 'itwiki', 'nlwiki', 'svwiki'];
const NON_LANG_WIKIS = ['commonswiki', 'specieswiki', 'metawiki', 'mediawikiwiki', 'wikidatawiki', 'wikimaniawiki'];
const suffixRE = new RegExp('\\s(' + IGNORE_SUFFIXES.join('|').replace(/\./g, '\\.') + ')$', 'i');
const PREVIEW_ERROR_HTML = '<em style="color:#b32424">Preview unavailable</em>';
// --- Constructors ---
function GetterDialog(config) {
GetterDialog.super.call(this, config);
this.abortController = null;
this.api = new mw.Api();
this.fieldsInitPromise = null;
this.pageTextPromise = null;
this.cachedResults = null;
this.items = [];
this.previewCache = {};
this.previewElements = new Map();
this.previewPending = new Set();
this.previewGeneration = 0;
this.setupUI();
}
OO.inheritClass(GetterDialog, OO.ui.ProcessDialog);
GetterDialog.static.name = 'getterDialog';
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
// --- Static Definitions ---
GetterDialog.static.name = 'getterDialog';
GetterDialog.static.title = 'Getter: Wikipedia List Builder';
GetterDialog.static.actions = [{ action: 'generate', label: 'Generate List', flags: ['primary', 'progressive'], modes: 'edit' }, { action: 'back', label: 'Go back', modes: 'result' }, { action: 'exit', label: 'Exit', flags: 'safe', modes: ['edit', 'result'] }];
// --- GetterDialog Prototype Methods (Alphabetical) ---
GetterDialog.prototype.autoPopulateFields = async function() {
const title = mw.config.get('wgTitle'),
clean = this.cleanTitle(title);
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const given = text.match(/\{\{\s*given name\b[^}]*\}\}/i),
surname = /\{\{\s*surname\s*(?:\||\}\})/i.test(text);
if (surname || (given && /\|\s*type\s*=\s*both\b/i.test(given[0]))) this.surnameTags.addTag(clean);
if (given) this.givenTags.addTag(clean);
if (!surname && !given) this.givenTags.addTag(clean);
} catch (e) { this.givenTags.addTag(clean); }
};
GetterDialog.prototype.batchFetchPreviews = async function(items) {
const generation = this.previewGeneration;
for (let i = 0; i < items.length; i += 50) {
const chunk = items.slice(i, i + 50);
chunk.forEach(item => this.previewPending.add(item.page.title));
try {
const text = chunk.map(item => `<div data-getter-preview="${mw.html.escape(item.page.title)}">{{anbl|${item.page.title}}}</div>`).join('\n');
const data = await this.api.post({ action: 'parse', text: text, contentmodel: 'wikitext', prop: 'text' });
if (generation !== this.previewGeneration) return;
const $results = $(data.parse.text['*']).find('[data-getter-preview]');
$results.each((_, el) => {
const title = el.dataset.getterPreview;
$(el).find('a').attr('target', '_blank').attr('rel', 'noopener');
const html = el.innerHTML;
if (title && this.previewPending.has(title)) {
this.previewCache[title] = html;
const targetEl = this.previewElements.get(title);
if (targetEl) targetEl.innerHTML = html;
}
});
} catch (e) {
console.error('[Getter] Batch parse error:', e);
if (generation === this.previewGeneration) this.setPreviewError(chunk);
} finally {
chunk.forEach(item => this.previewPending.delete(item.page.title));
}
}
};
GetterDialog.prototype.cleanTitle = (t) => t.replace(suffixRE, '').replace(/\s?\([^)]*\)$/, '').trim();
GetterDialog.prototype.compileCriteria = (vals) => vals.map(v => v.trim()).filter(Boolean).map(v => new RegExp('^' + v.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\?/g, '.').replace(/\*/g, '.*') + '$', 'i'));
GetterDialog.prototype.copyToClipboard = async function(text) {
if (window.isSecureContext && navigator.clipboard?.writeText) {
try { await navigator.clipboard.writeText(text); return true; } catch { /* empty */ }
}
const $ta = $('<textarea>').val(text).css({ position: 'fixed', top: '-1000px', left: '-1000px' }).appendTo(document.body);
const el = $ta[0];
el.focus();
el.select();
const success = document.execCommand('copy');
$ta.remove();
return success;
};
GetterDialog.prototype.createButtonRow = function(colType) {
const $row = $('<div>').css({ display: 'flex', 'flex-wrap': 'wrap', gap: '5px', 'margin-top': '10px' });
const performCopy = async (shouldExit) => {
console.log('performCopy triggered for:', colType);
if (!this.level || !this.mode) {
mw.notify('Formatting widgets not ready yet.', { type: 'error' });
return;
}
const selectedItems = (colType === 'list') ? this.items.filter(i => i.status !== null) : this.items;
if (selectedItems.length === 0) {
mw.notify('No items to copy.', { type: 'warn' });
return;
}
try {
const level = this.level.getValue();
const mode = this.mode.getValue();
const getText = (i) => {
if (colType === 'list') return i.status === 'wikitext' ? i.wikitext : i.anbl;
if (colType === 'wikitext') return i.wikitext;
return i.anbl;
};
const formatted = this.formatListWithHeadings(selectedItems, level, mode, getText);
const success = await this.copyToClipboard(formatted);
if (success) {
mw.notify(`Copied ${selectedItems.length} items!`, { type: 'success' });
if (shouldExit) this.close();
}
} catch (e) {
console.error('Copy error:', e);
mw.notify('Failed to copy list.', { type: 'error' });
}
};
const copyBtn = new OO.ui.ButtonWidget({ label: 'Copy' });
copyBtn.on('click', () => performCopy(false));
$row.append(copyBtn.$element);
const exitBtn = new OO.ui.ButtonWidget({ label: 'Copy & Exit', flags: ['progressive'] });
exitBtn.on('click', () => performCopy(true));
$row.append(exitBtn.$element);
if (colType === 'wikitext' || colType === 'anbl') {
const toggleBtn = new OO.ui.ButtonWidget({ label: 'Toggle All' });
toggleBtn.on('click', () => {
const allSet = this.items.every(i => i.status === colType);
this.items.forEach(i => i.status = allSet ? null : colType);
this.refreshUI();
});
const smartBtn = new OO.ui.ButtonWidget({ label: 'Smart Add' });
smartBtn.on('click', () => {
this.items.forEach(i => { if (!i.status) i.status = colType; });
this.refreshUI();
});
$row.append(toggleBtn.$element, smartBtn.$element);
} else if (colType === 'list') {
const clearBtn = new OO.ui.ButtonWidget({ label: 'Clear', flags: ['destructive'] });
clearBtn.on('click', () => {
this.items.forEach(i => i.status = null);
this.refreshUI();
});
$row.append(clearBtn.$element);
}
return $row;
};
GetterDialog.prototype.ensureFieldsInitialized = function() {
if (!this.fieldsInitPromise) {
this.fieldsInitPromise = (async () => {
this.givenTags.clearItems();
this.middleTags.clearItems();
this.surnameTags.clearItems();
await this.autoPopulateFields();
})();
}
return this.fieldsInitPromise;
};
GetterDialog.prototype.fetchEntities = async function(qids, signal) {
const all = {};
for (let i = 0; i < qids.length; i += 50) {
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');
const res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qids.slice(i, i + 50).join('|')}&props=claims|labels|descriptions|sitelinks&languages=en&format=json&origin=*`, { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
Object.assign(all, data.entities || {});
}
return all;
};
GetterDialog.prototype.findCandidates = async function(bestAnchor, compiled, excludeList, existing, signal) {
const resultsMap = new Map();
const term = bestAnchor.term.replace(/"/g, '\\"').trim();
const isWildcard = /[*?]/.test(term);
const DEBUG = true;
let searches = [];
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
searches.push(`intitle:${term}`);
} else if (isWildcard) {
searches = [`intitle:${term.replace(/\?/g, '*')}`, `intitle:"${term.replace(/[*?]/g, '')}"`];
} else {
searches = [`intitle:"${term}"`];
}
const MAX_CONTINUATIONS = 20;
const runSearch = async (srsearch) => {
let cont = {};
let continuations = 0;
let fetched = 0;
try {
do {
const data = await this.api.get({
action: 'query',
list: 'search',
srsearch,
srlimit: 'max',
sroffset: cont.sroffset || 0,
format: 'json'
}, { signal });
const results = data.query?.search || [];
const aarons = results.filter(r => r.title.startsWith('Aaron')).map(r => r.title);
if (aarons.length) {
console.log(`[${srsearch}] offset=${cont.sroffset || 0}`, aarons);
}
for (const p of results) {
const title = p.title.replace(/_/g, ' ').trim();
if (title.startsWith('Aaron')) {
console.log('Checking', title);
console.log({
existing: existing.has(title),
alreadyFound: resultsMap.has(title),
excluded: excludeList.some(e => title.toLowerCase().includes(e)),
matches: this.matchesCriteria(title, compiled)
});
}
if (existing.has(title) || resultsMap.has(title)) continue;
if (excludeList.some(e => title.toLowerCase().includes(e))) continue;
if (this.matchesCriteria(title, compiled)) {
resultsMap.set(title, {
title,
pageid: p.pageid
});
}
if (title.includes('Rahsaan')) {
console.log('Found Rahsaan', title);
}
}
cont = data.continue || {};
continuations++;
} while (cont.sroffset && continuations < MAX_CONTINUATIONS && !signal?.aborted);
} catch (e) {
if (e.name === 'AbortError') {
throw e;
}
console.error(`[Getter] Search error for "${srsearch}":`, e);
}
if (DEBUG) {
console.log(`[Getter] "${srsearch}" → ${fetched} hits, ${resultsMap.size} candidates`);
}
};
for (const srsearch of searches) {
if (signal?.aborted) {
break;
}
await runSearch(srsearch);
}
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
if (DEBUG) {
console.log(`[Getter] Starting A–Z boost for "${term}"`);
}
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (const letter of alphabet) {
if (signal?.aborted) {
break;
}
await runSearch(`intitle:${letter} intitle:${term}`);
}
}
if (DEBUG) {
console.log(`[Getter] Final candidates for "${term}": ${resultsMap.size}`);
}
return resultsMap;
};
GetterDialog.prototype.formatListWithHeadings = function(items, level, mode, getText) {
if (mode === 'none') return items.map(getText).join('\n');
if (mode === 'smart' && items.length > 0) {
const firstChar = items[0].sortKey.charAt(0).toUpperCase();
if (items.every(i => i.sortKey.charAt(0).toUpperCase() === firstChar)) {
return items.map(getText).join('\n');
}
}
const prefix = '='.repeat(level);
let segments = [];
if (mode === 'alpha') {
const groups = {};
items.forEach(i => {
const char = i.sortKey.charAt(0).toUpperCase();
if (!groups[char]) groups[char] = [];
groups[char].push(i);
});
Object.keys(groups).sort().forEach(char => segments.push({ title: char, items: groups[char] }));
} else {
const count = items.length;
const num = count > 200 ? 26 : (count > 150 ? 3 : 2);
const chunkSize = Math.floor(count / num);
let start = 0;
for (let i = 0; i < num && start < count; i++) {
let end = (i === num - 1) ? count : start + chunkSize;
while (end < count && items[end - 1].sortKey.charAt(0) === items[end].sortKey.charAt(0)) end++;
const chunk = items.slice(start, end);
if (chunk.length) {
const title = this.getSmartHeading(chunk[0], chunk[chunk.length - 1]);
segments.push({ title, items: chunk });
}
start = end;
}
}
return segments.map(s => `${prefix} ${s.title} ${prefix}\n${s.items.map(getText).join('\n')}`).join('\n\n');
};
GetterDialog.prototype.formatResult = function(p, mode, includeIll) {
if (mode === 'anbl') return `* {{anbl|${p.title}}}`;
const bY = p.ent?.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const dY = p.ent?.claims?.P570?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const bio = bY ? (dY ? ` (${bY}–${dY})` : ` (born ${bY})`) : '';
const rawDesc = (p.desc || '').replace(/\s?\(\s*(?:born|d\.)?\s*\d{4}(?:\s*–\s*\d{4})?\s*\)$/i, '').trim();
const suffix = rawDesc ? `, ${rawDesc}` : '';
let display = `[[${p.title}]]`;
if (includeIll) {
const ill = this.getPreferredSitelink(p.ent);
if (ill) display = `{{ill|${p.title}|${ill.lang}|${ill.title}}}`;
}
return `* ${display}${bio}${suffix}`;
};
GetterDialog.prototype.getActionProcess = function(action) {
if (action === 'generate') return new OO.ui.Process(async () => { await this.runGetter(); });
if (action === 'back') {
if (this.abortController) this.abortController.abort();
return new OO.ui.Process(() => {
this.resultContainer.$element.hide();
this.fieldset.$element.show();
this.actions.setMode('edit');
});
}
if (action === 'exit') { if (this.abortController) this.abortController.abort(); return new OO.ui.Process(() => this.close()); }
return GetterDialog.super.prototype.getActionProcess.call(this, action);
};
GetterDialog.prototype.getBestAnchor = function() {
const getValues = (w) => w.getValue().map(t => t.trim()).filter(Boolean);
const candidates = [...getValues(this.surnameTags).map(t => ({ term: t, field: 'surname', weight: 2000 + this.score(t) })), ...getValues(this.givenTags).map(t => ({ term: t, field: 'given', weight: 1000 + this.score(t) })), ...getValues(this.middleTags).map(t => ({ term: t, field: 'middle', weight: 0 + this.score(t) }))];
return candidates.length === 0 ? null : candidates.reduce((b, c) => (c.weight > b.weight ? c : b));
};
GetterDialog.prototype.getCompiledCriteria = function() { return { given: this.compileCriteria(this.givenTags.getValue()), surname: this.compileCriteria(this.surnameTags.getValue()), middle: this.compileCriteria(this.middleTags.getValue()) }; };
GetterDialog.prototype.getExistingLinks = async function() {
const titles = new Set();
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const regexes = [/\[\[([^\]|#]+)/g, /\{\{(?:anbl|Annotated biography link)\|([^}|]+)/gi];
regexes.forEach(re => { let m; while ((m = re.exec(text)) !== null) titles.add(this.normalizeTitle(m[1])); });
} catch (e) { console.error('[Getter] Parse error:', e); }
return titles;
};
GetterDialog.prototype.getPageText = function() { return this.pageTextPromise ??= this.api.get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'wikitext', format: 'json' }); };
GetterDialog.prototype.getPreferredSitelink = function(ent) {
const sitelinks = ent.sitelinks || {};
const key = LANG_PREFS.find(l => sitelinks[l]) || Object.keys(sitelinks).find(k => k.endsWith('wiki') && k !== 'enwiki' && !NON_LANG_WIKIS.includes(k));
return key ? { lang: key.replace(/wiki$/, ''), title: sitelinks[key].title } : null;
};
GetterDialog.prototype.getSetupProcess = function(data) {
return GetterDialog.super.prototype.getSetupProcess.call(this, data).next(() => {
if (data && data.pageTitle) {
if (this.givenTags.getValue().indexOf(data.pageTitle) === -1) {
this.givenTags.addTag(data.pageTitle);
}
}
this.autoPopulateFields();
});
};
GetterDialog.prototype.getSmartHeading = function(startItem, endItem) {
const char1 = startItem.sortKey.charAt(0).toUpperCase();
const char2 = endItem.sortKey.charAt(0).toUpperCase();
return char1 === char2 ? char1 : `${char1}–${char2}`;
};
GetterDialog.prototype.getSortKey = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return parts.length <= 1 ? clean.toLowerCase() : (parts[parts.length - 1] + ', ' + parts.slice(0, -1).join(' ') + ' | ' + clean).toLowerCase();
};
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
GetterDialog.prototype.matchesCriteria = function(title, compiled) {
const parts = this.tokenizeTitle(title);
const check = (val, regs) => regs.length === 0 || regs.some(re => re.test(val));
let givenMatch = check(parts.given, compiled.given);
if (!givenMatch && compiled.given.length > 0) {
const allTokens = this.cleanTitle(title).toLowerCase().split(/\s+/);
givenMatch = compiled.given.some(re => allTokens.some(token => re.test(token)));
}
const surnameMatch = check(parts.surname, compiled.surname);
const middleMatch = compiled.middle.length === 0 || compiled.middle.some(re => parts.middle.some(t => re.test(t)));
if (title.startsWith('Aaron')) {
console.log({
title,
parts,
givenMatch,
surnameMatch,
middleMatch,
compiled
});
}
return givenMatch && surnameMatch && middleMatch;
};
GetterDialog.prototype.normalizeTitle = (t) => { const title = mw.Title.newFromText(t); return title ? title.getPrefixedText() : t.replace(/_/g, ' ').trim(); };
GetterDialog.prototype.processEntityBatch = async function(pageIds, signal) {
const confirmed = new Map();
const pD = await this.api.get({ action: 'query', prop: 'pageprops|description', pageids: pageIds.join('|'), format: 'json' }, { signal });
const qidMap = {},
qidList = [];
Object.values(pD.query.pages).forEach(p => {
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p;
}
});
if (!qidList.length) return confirmed;
const entities = await this.fetchEntities(qidList, signal);
for (const qid in entities) {
const ent = entities[qid],
p = qidMap[qid],
isH = ent.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
if ((this.humanOnly.isSelected() && !isH) || (this.excludeHumans.isSelected() && isH)) continue;
confirmed.set(p.title, { title: p.title, desc: p.description || ent.descriptions?.en?.value || '', ent });
}
return confirmed;
};
GetterDialog.prototype.refreshPreviews = function() {
this.previewCache = {};
this.previewPending.clear();
this.previewGeneration++;
this.refreshUI();
};
GetterDialog.prototype.refreshUI = function() {
this.col1.empty();
this.col2.empty();
this.col3.empty();
this.col4.empty();
this.previewElements.clear();
const $ul = $('<ul>').css({ 'list-style-type': 'disc', 'padding-left': '20px', margin: '0' });
this.col4.append($ul);
const fSize = window.GetterConfig.fontSize;
const itemStyle = {
cursor: 'pointer',
padding: '2px',
'font-size': fSize,
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
};
this.items.forEach(item => {
const $w = $('<div>').text(item.wikitext).attr('title', item.wikitext).css({ ...itemStyle, opacity: item.status === 'wikitext' ? 0.3 : 1 });
$w.on('click', () => {
item.status = (item.status === 'wikitext' ? null : 'wikitext');
this.refreshUI();
});
this.col1.append($w);
const $a = $('<div>').text(item.anbl).attr('title', item.anbl).css({ ...itemStyle, opacity: item.status === 'anbl' ? 0.3 : 1 });
$a.on('click', () => {
item.status = (item.status === 'anbl' ? null : 'anbl');
this.refreshUI();
});
this.col3.append($a);
const $li = $('<li>').attr('title', item.page.title).css({
'font-size': fSize,
'margin-bottom': '2px',
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
}).html(this.previewCache[item.page.title] || '<em>Loading...</em>');
$ul.append($li);
this.previewElements.set(item.page.title, $li[0]);
});
this.items.filter(i => i.status).forEach(item => {
const text = item.status === 'wikitext' ? item.wikitext : item.anbl;
this.col2.append($('<div>').text(text).attr('title', text).css({ 'margin-bottom': '4px', 'font-size': fSize }));
});
const missing = this.items.filter(i => !(i.page.title in this.previewCache) && !this.previewPending.has(i.page.title));
if (missing.length) this.batchFetchPreviews(missing);
};
GetterDialog.prototype.renderResults = async function(confirmed) {
this.previewGeneration++;
this.previewPending.clear();
this.items = Array.from(confirmed.values()).map(p => ({
page: p,
sortKey: this.sortBySurname.isSelected() ? this.getSortKey(p.title) : this.cleanTitle(p.title).toLowerCase(),
wikitext: this.formatResult(p, 'wiki', this.includeIll.isSelected()),
anbl: this.formatResult(p, 'anbl', this.includeIll.isSelected()),
status: null
})).sort((a, b) => a.sortKey.localeCompare(b.sortKey, undefined, { sensitivity: 'base' }));
this.refreshUI();
this.fieldset.$element.hide();
this.resultContainer.$element.show();
this.resultControls.$element.show();
this.actions.setMode('result');
};
GetterDialog.prototype.runGetter = async function() {
this.abortController = new AbortController();
this.cachedResults = null;
try {
const best = this.getBestAnchor();
if (!best) { mw.notify('Please enter search terms.', { type: 'error' }); return; }
const existing = this.updateMode.isSelected() ? await this.getExistingLinks() : new Set();
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), this.excludeTags.getValue().map(e => e.trim().toLowerCase()), existing, this.abortController.signal);
const confirmed = new Map(),
pageIds = Array.from(resultsMap.values()).map(p => p.pageid);
for (let i = 0; i < pageIds.length; i += 50) {
const batchConfirmed = await this.processEntityBatch(pageIds.slice(i, i + 50), this.abortController.signal);
batchConfirmed.forEach((val, key) => confirmed.set(key, val));
}
this.cachedResults = confirmed;
this.renderResults(confirmed);
} catch (e) { if (e.name !== 'AbortError') mw.notify('Error generating list.', { type: 'error' }); } finally { this.abortController = null; }
};
GetterDialog.prototype.score = (term) => { const w = (term.match(/[*?]/g) || []).length; return (w === 0 ? 1000 : 0) + term.replace(/[*?]/g, '').length - w; };
GetterDialog.prototype.setPreviewError = function(items) {
items.forEach(item => {
this.previewCache[item.page.title] ??= PREVIEW_ERROR_HTML;
const targetEl = this.previewElements.get(item.page.title);
if (targetEl) targetEl.innerHTML = this.previewCache[item.page.title];
});
};
GetterDialog.prototype.setupUI = function() {
this.givenTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Given names...', allowArbitrary: true });
this.middleTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Middle terms...', allowArbitrary: true });
this.surnameTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Surnames...', allowArbitrary: true });
this.excludeTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Exclude strings...', allowArbitrary: true });
this.humanOnly = new OO.ui.CheckboxInputWidget({ selected: true });
this.excludeHumans = new OO.ui.CheckboxInputWidget({ selected: false });
this.includeIll = new OO.ui.CheckboxInputWidget({ selected: false });
this.sortBySurname = new OO.ui.CheckboxInputWidget({ selected: true });
this.updateMode = new OO.ui.CheckboxInputWidget({ selected: false });
this.mode = new OO.ui.RadioSelectInputWidget({
options: [{ data: 'none', label: 'No headings' }, { data: 'smart', label: 'Smart grouping' }, { data: 'alpha', label: 'A-Z' }],
value: 'none'
});
this.level = new OO.ui.RadioSelectInputWidget({
options: [{ data: 2, label: 'Level 2' }, { data: 3, label: 'Level 3' }, { data: 4, label: 'Level 4' }],
value: 3
});
if (!this.level || !this.mode) {
console.error('Level or mode widget missing during UI setup');
}
mw.util.addCSS(` .getter-result-fieldset .oo-ui-fieldsetLayout-header {
font-size: 13px !important;
color: #444;
}
.getter-formatting-container {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 40px;
}
.getter-formatting-container .oo-ui-fieldLayout {
display: flex;
align-items: center;
margin-top: 2px;
}
.getter-formatting-container .oo-ui-fieldLayout-label {
margin-right: 12px;
font-weight: bold;
width: auto;
flex-shrink: 0;
line-height: 1.4;
}
.oo-ui-radioSelectInputWidget {
vertical-align: middle;
}
`);
this.resultControls = new OO.ui.FieldsetLayout({ label: 'Output Formatting', classes: ['getter-result-fieldset'] });
const $formattingRow = $('<div>').addClass('getter-formatting-container');
const modeLayout = new OO.ui.FieldLayout(this.mode, {
label: 'Grouping Mode',
align: 'top'
});
const levelLayout = new OO.ui.FieldLayout(this.level, {
label: 'Heading Level',
align: 'top'
});
$formattingRow.append(modeLayout.$element, levelLayout.$element);
this.resultControls.$element.append($formattingRow);
this.fieldset = new OO.ui.FieldsetLayout({ label: 'Advanced Filter Settings' });
this.fieldset.addItems([
new OO.ui.FieldLayout(this.givenTags, { label: 'Given names', align: 'top' }),
new OO.ui.FieldLayout(this.middleTags, { label: 'Middle terms', align: 'top' }),
new OO.ui.FieldLayout(this.surnameTags, { label: 'Surnames', align: 'top' }),
new OO.ui.FieldLayout(this.excludeTags, { label: 'Exclude strings', align: 'top' }),
new OO.ui.FieldLayout(this.humanOnly, { label: 'Humans only', align: 'inline' }),
new OO.ui.FieldLayout(this.excludeHumans, { label: 'Exclude humans', align: 'inline' }),
new OO.ui.FieldLayout(this.sortBySurname, { label: 'Sort by surname', align: 'inline' }),
new OO.ui.FieldLayout(this.updateMode, { label: 'Exclude existing links', align: 'inline' }),
new OO.ui.FieldLayout(this.includeIll, { label: 'Include {{ill}}', align: 'inline' })
]);
const colStyle = { height: '480px', 'overflow-y': 'auto', 'overflow-x': 'hidden', border: '1px solid #ccc', padding: '10px', 'white-space': 'nowrap', 'text-overflow': 'ellipsis' };
this.col1 = $('<div>').css(colStyle);
this.col2 = $('<div>').css({ ...colStyle, background: '#f9f9f9' });
this.col3 = $('<div>').css(colStyle);
this.col4 = $('<div>').css(colStyle);
const columns = [this.col1, this.col2, this.col3, this.col4];
columns.forEach(col => {
col.on('scroll', (e) => {
const scrollTop = e.target.scrollTop;
columns.forEach(otherCol => { if (otherCol[0] !== e.target && otherCol[0].scrollTop !== scrollTop) otherCol[0].scrollTop = scrollTop; });
});
});
const $refreshBtn = new OO.ui.ButtonWidget({ label: 'Refresh', icon: 'reload', flags: ['progressive'] });
$refreshBtn.on('click', () => this.refreshPreviews());
this.resultContainer = new OO.ui.PanelLayout({ padded: true, expanded: true });
const $columns = $('<div>').css({ display: 'flex', gap: '10px' });
const createHeader = (label) => new OO.ui.LabelWidget({ label: label }).$element.css({ 'font-weight': 'bold', 'font-size': '13px', 'margin-bottom': '5px' });
$columns.append($('<div>').css('flex', '1.2').append(createHeader('Wikitext'), this.col1, this.createButtonRow('wikitext')), $('<div>').css('flex', '1.2').append(createHeader('Chosen List'), this.col2, this.createButtonRow('list')), $('<div>').css('flex', '0.8').append(createHeader('Anbl'), this.col3, this.createButtonRow('anbl')), $('<div>').css('flex', '1.2').append(createHeader('Anbl Preview'), this.col4, $('<div>').css('margin-top', '10px').append($refreshBtn.$element)));
this.resultContainer.$element.append($columns, $('<div>').css('margin-top', '20px').append(this.resultControls.$element)).hide();
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.panel.$element.append(this.fieldset.$element, this.resultContainer.$element);
};
GetterDialog.prototype.silentScan = async function() {
if (window.GetterConfig.enableSilentScan === false || !/(?:list of|surname|given name)/i.test(mw.config.get('wgTitle'))) return;
$('.mw-notification[data-notification-tag="getter-discovery"]').remove();
await this.ensureFieldsInitialized();
const best = this.getBestAnchor();
if (!best) return;
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), [], await this.getExistingLinks(), null);
if (!resultsMap.size) return;
const confirmed = await this.processEntityBatch(Array.from(resultsMap.values()).map(p => p.pageid).slice(0, 50), null);
if (confirmed.size > 0) {
this.cachedResults = confirmed;
const $link = $('<a>', { text: 'Review', href: '#', style: 'font-weight:bold; cursor:pointer' });
$link.on('click', (e) => {
e.preventDefault();
$link.closest('.mw-notification').remove();
this.windowManager.openWindow(this);
this.renderResults(this.cachedResults);
});
mw.notify($('<span>').text(`Getter found ${resultsMap.size > 50 ? '50+' : confirmed.size} potential entries. `).append($link), { tag: 'getter-discovery', autoHide: false });
}
};
GetterDialog.prototype.tokenizeTitle = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return {
given: parts[0] || '',
surname: parts.length > 1 ? parts[parts.length - 1] : '',
middle: parts.length > 2 ? parts.slice(1, -1) : []
};
};
// --- Initialization ---
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui', 'oojs-ui-widgets', 'oojs-ui-windows']).then(() => {
const wm = new OO.ui.WindowManager();
$(document.body).append(wm.$element);
const dialog = new GetterDialog({ size: 'full' });
dialog.windowManager = wm;
wm.addWindows([dialog]);
$(mw.util.addPortletLink('p-tb', '#', 'Getter', 't-getter')).on('click', (e) => {
e.preventDefault();
wm.openWindow(dialog);
});
});
}(mediaWiki, jQuery, OO));
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "0.5",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 10,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
/* global ComradeLib, mw, $ */
(async function() {
try {
await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-options.js&action=raw&ctype=text/javascript');
} catch (e) {
console.warn("Comrade: Could not load /comrade-options.js, using fallback defaults.", e);
}
window.ComradeOptions = Object.assign({
version: "0.5.0",
excludedTemplates: [],
nudgeTimeout: 10000,
debugMode: false
}, window.ComradeOptions || {});
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 res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qid}&props=claims|descriptions|labels&languages=en&format=json&origin=*`).then(r => r.json());
return res.entities[qid];
}
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;
ComradeLib.checkLeadCapitalization(currentTitle);
// 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);
await ComradeLib.checkNoShortDesc(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 isRedirect = !!mw.config.get("wgIsRedirect");
const userExclusions = window.comradeQualityExclusions || [];
const allExclusions = [...window.ComradeOptions.excludedTemplates, ...userExclusions];
const exclusionRegexString = allExclusions.map(t => t.replace("Template:", "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
const exclusionRegex = new RegExp(exclusionRegexString, 'i');
if (isRedirect || exclusionRegex.test(wikitext)) {
console.log("Comrade check skipped: Page is a redirect or excluded via options.");
return;
}
ComradeLib.checkQualityConsistency();
} catch (err) {
console.error("Comrade error:", err);
}
}
init();
})();
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Search') {
return;
}
mw.loader.using(['mediawiki.util']).then(function() {
$(function() {
const $resultsInfo = $('.results-info');
const totalResultsRaw = $resultsInfo.attr('data-mw-num-results-total') || '0';
function formatWithCommas(numberString) {
return numberString.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
const state = {
language: mw.config.get('wgContentLanguage'),
totalResultsFormatted: formatWithCommas(totalResultsRaw)
};
let titlesCache = null;
function extractResultTitles() {
const currentResults = $('.mw-search-result-heading');
if (!titlesCache || titlesCache.length !== currentResults.length) {
titlesCache = currentResults.map(function() {
return $(this).find('a:first').attr('title');
}).get().filter(Boolean);
}
return titlesCache;
}
mw.hook("wikipage.content").add(function() {
titlesCache = null;
});
function addLimitOptions() {
const selects = document.querySelectorAll('.cdx-table-pager__limit-form select[name="limit"]');
if (!selects.length) return;
const valuesToAdd = ["1000", "2500", "5000"];
selects.forEach(function(select) {
valuesToAdd.forEach(function(value) {
if (select.querySelector('option[value="' + value + '"]')) return;
const option = document.createElement("option");
option.value = value;
option.textContent = formatWithCommas(value) + " items";
const existingOptions = Array.from(select.querySelectorAll("option"));
const insertBefore = existingOptions.find(function(opt) {
return Number(opt.value) > Number(value);
});
if (insertBefore) {
select.insertBefore(option, insertBefore);
} else {
select.appendChild(option);
}
});
});
}
mw.hook("wikipage.content").add(addLimitOptions);
const numLinks = $('.mw-numlink');
if (numLinks.length > 4 && !numLinks.eq(4).next().is('a[href*="limit=5000"]')) {
const $firstAnchor = numLinks.eq(4);
const $firstSpan = $('<span> | </span>').insertAfter($firstAnchor);
const url1 = new URL(window.location.href);
url1.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url1.toString()
}).insertAfter($firstAnchor).before($firstSpan);
}
if (numLinks.length > 9 && !numLinks.eq(9).next().is('a[href*="limit=5000"]')) {
const $secondAnchor = numLinks.eq(9);
const $secondSpan = $('<span> | </span>').insertAfter($secondAnchor);
const url2 = new URL(window.location.href);
url2.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url2.toString()
}).insertAfter($secondAnchor).before($secondSpan);
}
const $container = $("<div></div>").addClass("enhanced-search-container").hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const interwikiResults = document.getElementById("mw-interwiki-results");
if (interwikiResults) {
$container.addClass("enhanced-search-floating").insertBefore("#mw-interwiki-results");
} else {
$container.addClass("enhanced-search-absolute").insertBefore(".searchresults");
}
const totalFormattedStr = state.totalResultsFormatted;
const $titleElement = $("<div>Show a shortened list of<br />this page's " + $('.mw-search-result-heading').length + " results <small>(of total of " + totalFormattedStr + ")</small></div>").addClass("enhanced-header").appendTo($container);
const $contentArea = $("<div></div>").addClass("enhanced-content-area").appendTo($container).hide();
const $note1 = $("<div>Copy this page\'s " + $('.mw-search-result-heading').length + " results to the clipboard as...</div>").addClass("enhanced-note").insertAfter($container).hide();
function setupHoverTooltip(element, message) {
$(element).attr('title', message).hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
}
const $note2 = $("<div>...plain text<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note1).hide();
setupHoverTooltip($note2, 'This button copies this page\'s results to the clipboard as plain text, i.e. without wikitext markup. Since this copies only the results shown on this search page, make sure you have clicked the "view 20, 50, 100, 250, 500, 5000" link. This format is compatible with PetScan and AutoWikiBrowser (AWB) for example.');
const $note3 = $("<div>...wikitext<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note2).hide();
setupHoverTooltip($note3, 'This button copies this page\'s results to the clipboard and also adds wikitext markup.');
const $note4 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note3).hide();
setupHoverTooltip($note4, 'This button copies a PetScan URL with this page\'s results to the clipboard. Note that if the PetScan URL becomes too long (~250+ pages), the URL might not work.');
const $note5 = $("<div>...or this search query's total results of " + totalFormattedStr + "<sup> (?)</sup> as...</div>").addClass("enhanced-note").insertAfter($note4).hide().attr('title', 'Note that the actual limit is 10,000, which is MediaWiki\'s maximum limit.').hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const $note6 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note5).hide();
setupHoverTooltip($note6, 'This button copies a PetScan URL with all ' + totalFormattedStr + ' results to the clipboard. Note that the maximum limit is 10,000 due to MediaWiki\'s restriction.');
const $note7 = $("<div><sup>(?)</sup> = hover over for tooltip details</div>").addClass("enhanced-note-right").insertAfter($note6).hide();
const noteElements = [$note1, $note2, $note3, $note4, $note5, $note6, $note7];
if (interwikiResults !== null) {
noteElements.forEach($el => $el.addClass("enhanced-floating-elements"));
} else {
noteElements.forEach(function($el, index) {
$el.addClass("enhanced-absolute-elements enhanced-position-" + index);
});
}
function formatPlain() {
return extractResultTitles().join("\r\n");
}
function formatWikitext() {
const titles = extractResultTitles().map(title => "* [[" + title + "]]");
const header = "This is a list of [" + window.location.href + " these search results] (made with the script [[User:JAAqqO/EnhancedSearchPage|EnhancedSearchPage]]):\r\n";
return header + titles.join("\r\n");
}
function formatPetScan() {
const encodedTitles = extractResultTitles().map(encodeURIComponent).join("%0A");
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&manual_list=" + encodedTitles + "&manual_list_wiki=" + state.language + "wiki&project=wikipedia&language=" + state.language + "&doit=";
}
function formatPetScanTotal() {
const oouiInput = document.getElementById("ooui-php-1");
const value = oouiInput ? oouiInput.value : "";
if (value) {
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&ns%5B0%5D=1&search_query=" + encodeURIComponent(value) + "&search_wiki=" + state.language + "wiki&search_max_results=10000&project=wikipedia&language=" + state.language + "&doit=";
}
return "";
}
function copyToClipboard(text) {
if (!text) return;
if (!navigator.clipboard) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
} catch (e) {
console.error('Fallback copy failed', e);
}
textarea.remove();
} else {
navigator.clipboard.writeText(text).catch(function(err) {
console.error("Copy failed: ", err);
});
}
}
function showCopiedMessage(event) {
const posX = event.pageX;
const posY = event.pageY;
const $msg = $("<div class='copied-message'>Copied!</div>").appendTo("body");
$msg.css({
position: "absolute",
top: (posY + 10) + "px",
left: (posX + 10) + "px",
backgroundColor: "#f0f0f0",
border: "1px solid grey",
padding: "5px 10px",
borderRadius: "5px",
zIndex: 9999
});
setTimeout(function() {
$msg.fadeOut("slow", function() {
$(this).remove();
});
}, 500);
}
function performButtonAnimation($element) {
$element.css("transform", "scaleY(0.9)").animate({
opacity: '0.5'
}, "fast", function() {
$(this).animate({
opacity: '1'
}, "slow").css("transform", "scaleY(1)");
});
}
let isExpanded = false;
function showContentArea() {
if (isExpanded) return;
isExpanded = true;
const currentResults = $('.mw-search-result-heading');
$contentArea.empty().append(currentResults.clone());
$contentArea.slideDown();
$(".searchalttitle").remove();
$container.addClass("enhanced-active-container");
$titleElement.remove();
noteElements.forEach($el => $el.show());
}
function bindCopyAction($element, formatterFn) {
$element.click(function(event) {
copyToClipboard(formatterFn());
showCopiedMessage(event);
performButtonAnimation($(this));
});
}
$container.click(showContentArea);
bindCopyAction($note2, formatPlain);
bindCopyAction($note3, formatWikitext);
bindCopyAction($note4, formatPetScan);
bindCopyAction($note6, formatPetScanTotal);
const $pageDown = $("<div><br /><br />[ ↓ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the bottom of the page');
}, function() {
$(this).removeClass("hover");
}).insertBefore('.mw-search-result:eq(0)');
const $pageUp = $("<div>[ ↑ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the top of the page');
}, function() {
$(this).removeClass("hover");
}).insertAfter('.mw-search-result:eq(-1)');
$pageDown.click(function(event) {
event.preventDefault();
$('html, body').animate({
scrollTop: $(document).height()
}, 500);
});
$pageUp.click(function() {
$('html, body').animate({
scrollTop: 0
}, 500);
});
$("<span> (<b>" + $('.mw-search-result-heading').length + "</b> shown on the page)</span>").appendTo('.results-info');
const searchDidYouMean = document.querySelector('.searchdidyoumean');
if (searchDidYouMean) {
searchDidYouMean.style.color = '#b32424';
searchDidYouMean.style.fontSize = '20px';
}
});
});
})();
//</nowiki>
gwr28kf2spkz7vhbp5lo1qjx8pyrmc6
769914
769913
2026-10-24T09:28:08Z
Sam Sailor
26820
Test
769914
javascript
text/javascript
//<nowiki>
(function(mw, $) {
'use strict';
if (mw.config.get('wgNamespaceNumber') !== 0) return;
$(async function() {
// Check if page belongs to Category:Unisex given names or contains the template
const categories = mw.config.get('wgCategories') || [];
const isUnisexPage = categories.some(c => c.includes('Unisex given names')) || $('.mw-parser-output').text().includes('unisex given name');
if (!isUnisexPage) return;
// Collect all article titles linked in lists/bullet points on the page
const titleMap = new Map(); // title -> $element
const titles = [];
$('#mw-content-text li a[title]').each(function() {
const $a = $(this);
const title = $a.attr('title');
// Skip red links, help pages, or main namespaces other than 0
if ($a.hasClass('new') || title.includes(':')) return;
if (!titleMap.has(title)) {
titleMap.set(title, $a);
titles.push(title);
}
});
if (titles.length === 0) return;
const api = new mw.Api();
// Batch query titles in chunks of 50 to get their wikibase item (QID)
for (let i = 0; i < titles.length; i += 50) {
const chunk = titles.slice(i, i + 50);
try {
const data = await api.get({
action: 'query',
titles: chunk.join('|'),
prop: 'pageprops',
redirects: true, // Enable MediaWiki redirect resolution
format: 'json'
});
const query = data.query || {};
const pages = query.pages || {};
const redirectMap = {}; // Maps original title/redirect -> final target title
// 1. Build a map if any redirects occurred
if (query.redirects) {
query.redirects.forEach(r => {
redirectMap[r.from] = r.to;
});
}
const qidMap = {};
const qidList = [];
Object.values(pages).forEach(p => {
// Ignore missing pages (negative page IDs)
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p.title;
}
});
if (!qidList.length) continue;
// Fetch P21 from Wikidata API
const wdRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qidList.join('|')}&props=claims&format=json&origin=*`);
const wdData = await wdRes.json();
const entities = wdData.entities || {};
for (const qid in entities) {
const canonicalTitle = qidMap[qid]; // The actual target page title
// Find all original DOM elements that targeted either the canonical title OR redirected to it
const matchingOriginalTitles = [canonicalTitle];
for (const [fromTitle, toTitle] of Object.entries(redirectMap)) {
if (toTitle === canonicalTitle) {
matchingOriginalTitles.push(fromTitle);
}
}
// Append symbol right after the link
matchingOriginalTitles.forEach(origTitle => {
const $a = titleMap.get(origTitle);
if (!$a) return;
const claims = entities[qid].claims || {};
const p21 = claims.P21?.[0]?.mainsnak?.datavalue?.value?.id;
let symbol = ' ?';
let titleAttr = 'Gender: Unknown or ambiguous';
if (p21 === 'Q6581097') {
symbol = ' ♂';
titleAttr = 'Gender: Male';
} else if (p21 === 'Q6581072') {
symbol = ' ♀';
titleAttr = 'Gender: Female';
}
// Avoid appending twice if multiple variants match
if ($a.next('.gender-symbol').length === 0) {
$('<span class="gender-symbol" style="font-size: 0.9em; margin-left: 4px; cursor: default;" title="' + titleAttr + '"></span>').text(symbol).insertAfter($a);
}
});
}
} catch (e) {
console.error('[UnisexGenderAppender] Error fetching batch:', e);
}
}
});
})(mediaWiki, jQuery);
(function(mw, $, OO) {
'use strict';
window.getterWindowManager = null;
window.GetterConfig = Object.assign({
enableSilentScan: true,
fontSize: '12px'
}, window.GetterConfig || {});
const IGNORE_SUFFIXES = ['Sr.', 'Sr', 'Jr.', 'Jr', 'II', 'III', 'IV', 'V', 'MD', 'PhD'];
const LANG_PREFS = ['frwiki', 'dewiki', 'eswiki', 'itwiki', 'nlwiki', 'svwiki'];
const NON_LANG_WIKIS = ['commonswiki', 'specieswiki', 'metawiki', 'mediawikiwiki', 'wikidatawiki', 'wikimaniawiki'];
const suffixRE = new RegExp('\\s(' + IGNORE_SUFFIXES.join('|').replace(/\./g, '\\.') + ')$', 'i');
const PREVIEW_ERROR_HTML = '<em style="color:#b32424">Preview unavailable</em>';
// --- Constructors ---
function GetterDialog(config) {
GetterDialog.super.call(this, config);
this.abortController = null;
this.api = new mw.Api();
this.fieldsInitPromise = null;
this.pageTextPromise = null;
this.cachedResults = null;
this.items = [];
this.previewCache = {};
this.previewElements = new Map();
this.previewPending = new Set();
this.previewGeneration = 0;
this.setupUI();
}
OO.inheritClass(GetterDialog, OO.ui.ProcessDialog);
GetterDialog.static.name = 'getterDialog';
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
// --- Static Definitions ---
GetterDialog.static.name = 'getterDialog';
GetterDialog.static.title = 'Getter: Wikipedia List Builder';
GetterDialog.static.actions = [{ action: 'generate', label: 'Generate List', flags: ['primary', 'progressive'], modes: 'edit' }, { action: 'back', label: 'Go back', modes: 'result' }, { action: 'exit', label: 'Exit', flags: 'safe', modes: ['edit', 'result'] }];
// --- GetterDialog Prototype Methods (Alphabetical) ---
GetterDialog.prototype.autoPopulateFields = async function() {
const title = mw.config.get('wgTitle'),
clean = this.cleanTitle(title);
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const given = text.match(/\{\{\s*given name\b[^}]*\}\}/i),
surname = /\{\{\s*surname\s*(?:\||\}\})/i.test(text);
if (surname || (given && /\|\s*type\s*=\s*both\b/i.test(given[0]))) this.surnameTags.addTag(clean);
if (given) this.givenTags.addTag(clean);
if (!surname && !given) this.givenTags.addTag(clean);
} catch (e) { this.givenTags.addTag(clean); }
};
GetterDialog.prototype.batchFetchPreviews = async function(items) {
const generation = this.previewGeneration;
for (let i = 0; i < items.length; i += 50) {
const chunk = items.slice(i, i + 50);
chunk.forEach(item => this.previewPending.add(item.page.title));
try {
const text = chunk.map(item => `<div data-getter-preview="${mw.html.escape(item.page.title)}">{{anbl|${item.page.title}}}</div>`).join('\n');
const data = await this.api.post({ action: 'parse', text: text, contentmodel: 'wikitext', prop: 'text' });
if (generation !== this.previewGeneration) return;
const $results = $(data.parse.text['*']).find('[data-getter-preview]');
$results.each((_, el) => {
const title = el.dataset.getterPreview;
$(el).find('a').attr('target', '_blank').attr('rel', 'noopener');
const html = el.innerHTML;
if (title && this.previewPending.has(title)) {
this.previewCache[title] = html;
const targetEl = this.previewElements.get(title);
if (targetEl) targetEl.innerHTML = html;
}
});
} catch (e) {
console.error('[Getter] Batch parse error:', e);
if (generation === this.previewGeneration) this.setPreviewError(chunk);
} finally {
chunk.forEach(item => this.previewPending.delete(item.page.title));
}
}
};
GetterDialog.prototype.cleanTitle = (t) => t.replace(suffixRE, '').replace(/\s?\([^)]*\)$/, '').trim();
GetterDialog.prototype.compileCriteria = (vals) => vals.map(v => v.trim()).filter(Boolean).map(v => new RegExp('^' + v.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\?/g, '.').replace(/\*/g, '.*') + '$', 'i'));
GetterDialog.prototype.copyToClipboard = async function(text) {
if (window.isSecureContext && navigator.clipboard?.writeText) {
try { await navigator.clipboard.writeText(text); return true; } catch { /* empty */ }
}
const $ta = $('<textarea>').val(text).css({ position: 'fixed', top: '-1000px', left: '-1000px' }).appendTo(document.body);
const el = $ta[0];
el.focus();
el.select();
const success = document.execCommand('copy');
$ta.remove();
return success;
};
GetterDialog.prototype.createButtonRow = function(colType) {
const $row = $('<div>').css({ display: 'flex', 'flex-wrap': 'wrap', gap: '5px', 'margin-top': '10px' });
const performCopy = async (shouldExit) => {
console.log('performCopy triggered for:', colType);
if (!this.level || !this.mode) {
mw.notify('Formatting widgets not ready yet.', { type: 'error' });
return;
}
const selectedItems = (colType === 'list') ? this.items.filter(i => i.status !== null) : this.items;
if (selectedItems.length === 0) {
mw.notify('No items to copy.', { type: 'warn' });
return;
}
try {
const level = this.level.getValue();
const mode = this.mode.getValue();
const getText = (i) => {
if (colType === 'list') return i.status === 'wikitext' ? i.wikitext : i.anbl;
if (colType === 'wikitext') return i.wikitext;
return i.anbl;
};
const formatted = this.formatListWithHeadings(selectedItems, level, mode, getText);
const success = await this.copyToClipboard(formatted);
if (success) {
mw.notify(`Copied ${selectedItems.length} items!`, { type: 'success' });
if (shouldExit) this.close();
}
} catch (e) {
console.error('Copy error:', e);
mw.notify('Failed to copy list.', { type: 'error' });
}
};
const copyBtn = new OO.ui.ButtonWidget({ label: 'Copy' });
copyBtn.on('click', () => performCopy(false));
$row.append(copyBtn.$element);
const exitBtn = new OO.ui.ButtonWidget({ label: 'Copy & Exit', flags: ['progressive'] });
exitBtn.on('click', () => performCopy(true));
$row.append(exitBtn.$element);
if (colType === 'wikitext' || colType === 'anbl') {
const toggleBtn = new OO.ui.ButtonWidget({ label: 'Toggle All' });
toggleBtn.on('click', () => {
const allSet = this.items.every(i => i.status === colType);
this.items.forEach(i => i.status = allSet ? null : colType);
this.refreshUI();
});
const smartBtn = new OO.ui.ButtonWidget({ label: 'Smart Add' });
smartBtn.on('click', () => {
this.items.forEach(i => { if (!i.status) i.status = colType; });
this.refreshUI();
});
$row.append(toggleBtn.$element, smartBtn.$element);
} else if (colType === 'list') {
const clearBtn = new OO.ui.ButtonWidget({ label: 'Clear', flags: ['destructive'] });
clearBtn.on('click', () => {
this.items.forEach(i => i.status = null);
this.refreshUI();
});
$row.append(clearBtn.$element);
}
return $row;
};
GetterDialog.prototype.ensureFieldsInitialized = function() {
if (!this.fieldsInitPromise) {
this.fieldsInitPromise = (async () => {
this.givenTags.clearItems();
this.middleTags.clearItems();
this.surnameTags.clearItems();
await this.autoPopulateFields();
})();
}
return this.fieldsInitPromise;
};
GetterDialog.prototype.fetchEntities = async function(qids, signal) {
const all = {};
for (let i = 0; i < qids.length; i += 50) {
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');
const res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qids.slice(i, i + 50).join('|')}&props=claims|labels|descriptions|sitelinks&languages=en&format=json&origin=*`, { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
Object.assign(all, data.entities || {});
}
return all;
};
GetterDialog.prototype.findCandidates = async function(bestAnchor, compiled, excludeList, existing, signal) {
const resultsMap = new Map();
const term = bestAnchor.term.replace(/"/g, '\\"').trim();
const isWildcard = /[*?]/.test(term);
const DEBUG = true;
let searches = [];
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
searches.push(`intitle:${term}`);
} else if (isWildcard) {
searches = [`intitle:${term.replace(/\?/g, '*')}`, `intitle:"${term.replace(/[*?]/g, '')}"`];
} else {
searches = [`intitle:"${term}"`];
}
const MAX_CONTINUATIONS = 20;
const runSearch = async (srsearch) => {
let cont = {};
let continuations = 0;
let fetched = 0;
try {
do {
const data = await this.api.get({
action: 'query',
list: 'search',
srsearch,
srlimit: 'max',
sroffset: cont.sroffset || 0,
format: 'json'
}, { signal });
const results = data.query?.search || [];
const aarons = results.filter(r => r.title.startsWith('Aaron')).map(r => r.title);
if (aarons.length) {
console.log(`[${srsearch}] offset=${cont.sroffset || 0}`, aarons);
}
for (const p of results) {
const title = p.title.replace(/_/g, ' ').trim();
if (title.startsWith('Aaron')) {
console.log('Checking', title);
console.log({
existing: existing.has(title),
alreadyFound: resultsMap.has(title),
excluded: excludeList.some(e => title.toLowerCase().includes(e)),
matches: this.matchesCriteria(title, compiled)
});
}
if (existing.has(title) || resultsMap.has(title)) continue;
if (excludeList.some(e => title.toLowerCase().includes(e))) continue;
if (this.matchesCriteria(title, compiled)) {
resultsMap.set(title, {
title,
pageid: p.pageid
});
}
if (title.includes('Rahsaan')) {
console.log('Found Rahsaan', title);
}
}
cont = data.continue || {};
continuations++;
} while (cont.sroffset && continuations < MAX_CONTINUATIONS && !signal?.aborted);
} catch (e) {
if (e.name === 'AbortError') {
throw e;
}
console.error(`[Getter] Search error for "${srsearch}":`, e);
}
if (DEBUG) {
console.log(`[Getter] "${srsearch}" → ${fetched} hits, ${resultsMap.size} candidates`);
}
};
for (const srsearch of searches) {
if (signal?.aborted) {
break;
}
await runSearch(srsearch);
}
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
if (DEBUG) {
console.log(`[Getter] Starting A–Z boost for "${term}"`);
}
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (const letter of alphabet) {
if (signal?.aborted) {
break;
}
await runSearch(`intitle:${letter} intitle:${term}`);
}
}
if (DEBUG) {
console.log(`[Getter] Final candidates for "${term}": ${resultsMap.size}`);
}
return resultsMap;
};
GetterDialog.prototype.formatListWithHeadings = function(items, level, mode, getText) {
if (mode === 'none') return items.map(getText).join('\n');
if (mode === 'smart' && items.length > 0) {
const firstChar = items[0].sortKey.charAt(0).toUpperCase();
if (items.every(i => i.sortKey.charAt(0).toUpperCase() === firstChar)) {
return items.map(getText).join('\n');
}
}
const prefix = '='.repeat(level);
let segments = [];
if (mode === 'alpha') {
const groups = {};
items.forEach(i => {
const char = i.sortKey.charAt(0).toUpperCase();
if (!groups[char]) groups[char] = [];
groups[char].push(i);
});
Object.keys(groups).sort().forEach(char => segments.push({ title: char, items: groups[char] }));
} else {
const count = items.length;
const num = count > 200 ? 26 : (count > 150 ? 3 : 2);
const chunkSize = Math.floor(count / num);
let start = 0;
for (let i = 0; i < num && start < count; i++) {
let end = (i === num - 1) ? count : start + chunkSize;
while (end < count && items[end - 1].sortKey.charAt(0) === items[end].sortKey.charAt(0)) end++;
const chunk = items.slice(start, end);
if (chunk.length) {
const title = this.getSmartHeading(chunk[0], chunk[chunk.length - 1]);
segments.push({ title, items: chunk });
}
start = end;
}
}
return segments.map(s => `${prefix} ${s.title} ${prefix}\n${s.items.map(getText).join('\n')}`).join('\n\n');
};
GetterDialog.prototype.formatResult = function(p, mode, includeIll) {
if (mode === 'anbl') return `* {{anbl|${p.title}}}`;
const bY = p.ent?.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const dY = p.ent?.claims?.P570?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const bio = bY ? (dY ? ` (${bY}–${dY})` : ` (born ${bY})`) : '';
const rawDesc = (p.desc || '').replace(/\s?\(\s*(?:born|d\.)?\s*\d{4}(?:\s*–\s*\d{4})?\s*\)$/i, '').trim();
const suffix = rawDesc ? `, ${rawDesc}` : '';
let display = `[[${p.title}]]`;
if (includeIll) {
const ill = this.getPreferredSitelink(p.ent);
if (ill) display = `{{ill|${p.title}|${ill.lang}|${ill.title}}}`;
}
return `* ${display}${bio}${suffix}`;
};
GetterDialog.prototype.getActionProcess = function(action) {
if (action === 'generate') return new OO.ui.Process(async () => { await this.runGetter(); });
if (action === 'back') {
if (this.abortController) this.abortController.abort();
return new OO.ui.Process(() => {
this.resultContainer.$element.hide();
this.fieldset.$element.show();
this.actions.setMode('edit');
});
}
if (action === 'exit') { if (this.abortController) this.abortController.abort(); return new OO.ui.Process(() => this.close()); }
return GetterDialog.super.prototype.getActionProcess.call(this, action);
};
GetterDialog.prototype.getBestAnchor = function() {
const getValues = (w) => w.getValue().map(t => t.trim()).filter(Boolean);
const candidates = [...getValues(this.surnameTags).map(t => ({ term: t, field: 'surname', weight: 2000 + this.score(t) })), ...getValues(this.givenTags).map(t => ({ term: t, field: 'given', weight: 1000 + this.score(t) })), ...getValues(this.middleTags).map(t => ({ term: t, field: 'middle', weight: 0 + this.score(t) }))];
return candidates.length === 0 ? null : candidates.reduce((b, c) => (c.weight > b.weight ? c : b));
};
GetterDialog.prototype.getCompiledCriteria = function() { return { given: this.compileCriteria(this.givenTags.getValue()), surname: this.compileCriteria(this.surnameTags.getValue()), middle: this.compileCriteria(this.middleTags.getValue()) }; };
GetterDialog.prototype.getExistingLinks = async function() {
const titles = new Set();
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const regexes = [/\[\[([^\]|#]+)/g, /\{\{(?:anbl|Annotated biography link)\|([^}|]+)/gi];
regexes.forEach(re => { let m; while ((m = re.exec(text)) !== null) titles.add(this.normalizeTitle(m[1])); });
} catch (e) { console.error('[Getter] Parse error:', e); }
return titles;
};
GetterDialog.prototype.getPageText = function() { return this.pageTextPromise ??= this.api.get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'wikitext', format: 'json' }); };
GetterDialog.prototype.getPreferredSitelink = function(ent) {
const sitelinks = ent.sitelinks || {};
const key = LANG_PREFS.find(l => sitelinks[l]) || Object.keys(sitelinks).find(k => k.endsWith('wiki') && k !== 'enwiki' && !NON_LANG_WIKIS.includes(k));
return key ? { lang: key.replace(/wiki$/, ''), title: sitelinks[key].title } : null;
};
GetterDialog.prototype.getSetupProcess = function(data) {
return GetterDialog.super.prototype.getSetupProcess.call(this, data).next(() => {
if (data && data.pageTitle) {
if (this.givenTags.getValue().indexOf(data.pageTitle) === -1) {
this.givenTags.addTag(data.pageTitle);
}
}
this.autoPopulateFields();
});
};
GetterDialog.prototype.getSmartHeading = function(startItem, endItem) {
const char1 = startItem.sortKey.charAt(0).toUpperCase();
const char2 = endItem.sortKey.charAt(0).toUpperCase();
return char1 === char2 ? char1 : `${char1}–${char2}`;
};
GetterDialog.prototype.getSortKey = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return parts.length <= 1 ? clean.toLowerCase() : (parts[parts.length - 1] + ', ' + parts.slice(0, -1).join(' ') + ' | ' + clean).toLowerCase();
};
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
GetterDialog.prototype.matchesCriteria = function(title, compiled) {
const parts = this.tokenizeTitle(title);
const check = (val, regs) => regs.length === 0 || regs.some(re => re.test(val));
let givenMatch = check(parts.given, compiled.given);
if (!givenMatch && compiled.given.length > 0) {
const allTokens = this.cleanTitle(title).toLowerCase().split(/\s+/);
givenMatch = compiled.given.some(re => allTokens.some(token => re.test(token)));
}
const surnameMatch = check(parts.surname, compiled.surname);
const middleMatch = compiled.middle.length === 0 || compiled.middle.some(re => parts.middle.some(t => re.test(t)));
if (title.startsWith('Aaron')) {
console.log({
title,
parts,
givenMatch,
surnameMatch,
middleMatch,
compiled
});
}
return givenMatch && surnameMatch && middleMatch;
};
GetterDialog.prototype.normalizeTitle = (t) => { const title = mw.Title.newFromText(t); return title ? title.getPrefixedText() : t.replace(/_/g, ' ').trim(); };
GetterDialog.prototype.processEntityBatch = async function(pageIds, signal) {
const confirmed = new Map();
const pD = await this.api.get({ action: 'query', prop: 'pageprops|description', pageids: pageIds.join('|'), format: 'json' }, { signal });
const qidMap = {},
qidList = [];
Object.values(pD.query.pages).forEach(p => {
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p;
}
});
if (!qidList.length) return confirmed;
const entities = await this.fetchEntities(qidList, signal);
for (const qid in entities) {
const ent = entities[qid],
p = qidMap[qid],
isH = ent.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
if ((this.humanOnly.isSelected() && !isH) || (this.excludeHumans.isSelected() && isH)) continue;
confirmed.set(p.title, { title: p.title, desc: p.description || ent.descriptions?.en?.value || '', ent });
}
return confirmed;
};
GetterDialog.prototype.refreshPreviews = function() {
this.previewCache = {};
this.previewPending.clear();
this.previewGeneration++;
this.refreshUI();
};
GetterDialog.prototype.refreshUI = function() {
this.col1.empty();
this.col2.empty();
this.col3.empty();
this.col4.empty();
this.previewElements.clear();
const $ul = $('<ul>').css({ 'list-style-type': 'disc', 'padding-left': '20px', margin: '0' });
this.col4.append($ul);
const fSize = window.GetterConfig.fontSize;
const itemStyle = {
cursor: 'pointer',
padding: '2px',
'font-size': fSize,
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
};
this.items.forEach(item => {
const $w = $('<div>').text(item.wikitext).attr('title', item.wikitext).css({ ...itemStyle, opacity: item.status === 'wikitext' ? 0.3 : 1 });
$w.on('click', () => {
item.status = (item.status === 'wikitext' ? null : 'wikitext');
this.refreshUI();
});
this.col1.append($w);
const $a = $('<div>').text(item.anbl).attr('title', item.anbl).css({ ...itemStyle, opacity: item.status === 'anbl' ? 0.3 : 1 });
$a.on('click', () => {
item.status = (item.status === 'anbl' ? null : 'anbl');
this.refreshUI();
});
this.col3.append($a);
const $li = $('<li>').attr('title', item.page.title).css({
'font-size': fSize,
'margin-bottom': '2px',
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
}).html(this.previewCache[item.page.title] || '<em>Loading...</em>');
$ul.append($li);
this.previewElements.set(item.page.title, $li[0]);
});
this.items.filter(i => i.status).forEach(item => {
const text = item.status === 'wikitext' ? item.wikitext : item.anbl;
this.col2.append($('<div>').text(text).attr('title', text).css({ 'margin-bottom': '4px', 'font-size': fSize }));
});
const missing = this.items.filter(i => !(i.page.title in this.previewCache) && !this.previewPending.has(i.page.title));
if (missing.length) this.batchFetchPreviews(missing);
};
GetterDialog.prototype.renderResults = async function(confirmed) {
this.previewGeneration++;
this.previewPending.clear();
this.items = Array.from(confirmed.values()).map(p => ({
page: p,
sortKey: this.sortBySurname.isSelected() ? this.getSortKey(p.title) : this.cleanTitle(p.title).toLowerCase(),
wikitext: this.formatResult(p, 'wiki', this.includeIll.isSelected()),
anbl: this.formatResult(p, 'anbl', this.includeIll.isSelected()),
status: null
})).sort((a, b) => a.sortKey.localeCompare(b.sortKey, undefined, { sensitivity: 'base' }));
this.refreshUI();
this.fieldset.$element.hide();
this.resultContainer.$element.show();
this.resultControls.$element.show();
this.actions.setMode('result');
};
GetterDialog.prototype.runGetter = async function() {
this.abortController = new AbortController();
this.cachedResults = null;
try {
const best = this.getBestAnchor();
if (!best) { mw.notify('Please enter search terms.', { type: 'error' }); return; }
const existing = this.updateMode.isSelected() ? await this.getExistingLinks() : new Set();
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), this.excludeTags.getValue().map(e => e.trim().toLowerCase()), existing, this.abortController.signal);
const confirmed = new Map(),
pageIds = Array.from(resultsMap.values()).map(p => p.pageid);
for (let i = 0; i < pageIds.length; i += 50) {
const batchConfirmed = await this.processEntityBatch(pageIds.slice(i, i + 50), this.abortController.signal);
batchConfirmed.forEach((val, key) => confirmed.set(key, val));
}
this.cachedResults = confirmed;
this.renderResults(confirmed);
} catch (e) { if (e.name !== 'AbortError') mw.notify('Error generating list.', { type: 'error' }); } finally { this.abortController = null; }
};
GetterDialog.prototype.score = (term) => { const w = (term.match(/[*?]/g) || []).length; return (w === 0 ? 1000 : 0) + term.replace(/[*?]/g, '').length - w; };
GetterDialog.prototype.setPreviewError = function(items) {
items.forEach(item => {
this.previewCache[item.page.title] ??= PREVIEW_ERROR_HTML;
const targetEl = this.previewElements.get(item.page.title);
if (targetEl) targetEl.innerHTML = this.previewCache[item.page.title];
});
};
GetterDialog.prototype.setupUI = function() {
this.givenTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Given names...', allowArbitrary: true });
this.middleTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Middle terms...', allowArbitrary: true });
this.surnameTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Surnames...', allowArbitrary: true });
this.excludeTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Exclude strings...', allowArbitrary: true });
this.humanOnly = new OO.ui.CheckboxInputWidget({ selected: true });
this.excludeHumans = new OO.ui.CheckboxInputWidget({ selected: false });
this.includeIll = new OO.ui.CheckboxInputWidget({ selected: false });
this.sortBySurname = new OO.ui.CheckboxInputWidget({ selected: true });
this.updateMode = new OO.ui.CheckboxInputWidget({ selected: false });
this.mode = new OO.ui.RadioSelectInputWidget({
options: [{ data: 'none', label: 'No headings' }, { data: 'smart', label: 'Smart grouping' }, { data: 'alpha', label: 'A-Z' }],
value: 'none'
});
this.level = new OO.ui.RadioSelectInputWidget({
options: [{ data: 2, label: 'Level 2' }, { data: 3, label: 'Level 3' }, { data: 4, label: 'Level 4' }],
value: 3
});
if (!this.level || !this.mode) {
console.error('Level or mode widget missing during UI setup');
}
mw.util.addCSS(` .getter-result-fieldset .oo-ui-fieldsetLayout-header {
font-size: 13px !important;
color: #444;
}
.getter-formatting-container {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 40px;
}
.getter-formatting-container .oo-ui-fieldLayout {
display: flex;
align-items: center;
margin-top: 2px;
}
.getter-formatting-container .oo-ui-fieldLayout-label {
margin-right: 12px;
font-weight: bold;
width: auto;
flex-shrink: 0;
line-height: 1.4;
}
.oo-ui-radioSelectInputWidget {
vertical-align: middle;
}
`);
this.resultControls = new OO.ui.FieldsetLayout({ label: 'Output Formatting', classes: ['getter-result-fieldset'] });
const $formattingRow = $('<div>').addClass('getter-formatting-container');
const modeLayout = new OO.ui.FieldLayout(this.mode, {
label: 'Grouping Mode',
align: 'top'
});
const levelLayout = new OO.ui.FieldLayout(this.level, {
label: 'Heading Level',
align: 'top'
});
$formattingRow.append(modeLayout.$element, levelLayout.$element);
this.resultControls.$element.append($formattingRow);
this.fieldset = new OO.ui.FieldsetLayout({ label: 'Advanced Filter Settings' });
this.fieldset.addItems([
new OO.ui.FieldLayout(this.givenTags, { label: 'Given names', align: 'top' }),
new OO.ui.FieldLayout(this.middleTags, { label: 'Middle terms', align: 'top' }),
new OO.ui.FieldLayout(this.surnameTags, { label: 'Surnames', align: 'top' }),
new OO.ui.FieldLayout(this.excludeTags, { label: 'Exclude strings', align: 'top' }),
new OO.ui.FieldLayout(this.humanOnly, { label: 'Humans only', align: 'inline' }),
new OO.ui.FieldLayout(this.excludeHumans, { label: 'Exclude humans', align: 'inline' }),
new OO.ui.FieldLayout(this.sortBySurname, { label: 'Sort by surname', align: 'inline' }),
new OO.ui.FieldLayout(this.updateMode, { label: 'Exclude existing links', align: 'inline' }),
new OO.ui.FieldLayout(this.includeIll, { label: 'Include {{ill}}', align: 'inline' })
]);
const colStyle = { height: '480px', 'overflow-y': 'auto', 'overflow-x': 'hidden', border: '1px solid #ccc', padding: '10px', 'white-space': 'nowrap', 'text-overflow': 'ellipsis' };
this.col1 = $('<div>').css(colStyle);
this.col2 = $('<div>').css({ ...colStyle, background: '#f9f9f9' });
this.col3 = $('<div>').css(colStyle);
this.col4 = $('<div>').css(colStyle);
const columns = [this.col1, this.col2, this.col3, this.col4];
columns.forEach(col => {
col.on('scroll', (e) => {
const scrollTop = e.target.scrollTop;
columns.forEach(otherCol => { if (otherCol[0] !== e.target && otherCol[0].scrollTop !== scrollTop) otherCol[0].scrollTop = scrollTop; });
});
});
const $refreshBtn = new OO.ui.ButtonWidget({ label: 'Refresh', icon: 'reload', flags: ['progressive'] });
$refreshBtn.on('click', () => this.refreshPreviews());
this.resultContainer = new OO.ui.PanelLayout({ padded: true, expanded: true });
const $columns = $('<div>').css({ display: 'flex', gap: '10px' });
const createHeader = (label) => new OO.ui.LabelWidget({ label: label }).$element.css({ 'font-weight': 'bold', 'font-size': '13px', 'margin-bottom': '5px' });
$columns.append($('<div>').css('flex', '1.2').append(createHeader('Wikitext'), this.col1, this.createButtonRow('wikitext')), $('<div>').css('flex', '1.2').append(createHeader('Chosen List'), this.col2, this.createButtonRow('list')), $('<div>').css('flex', '0.8').append(createHeader('Anbl'), this.col3, this.createButtonRow('anbl')), $('<div>').css('flex', '1.2').append(createHeader('Anbl Preview'), this.col4, $('<div>').css('margin-top', '10px').append($refreshBtn.$element)));
this.resultContainer.$element.append($columns, $('<div>').css('margin-top', '20px').append(this.resultControls.$element)).hide();
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.panel.$element.append(this.fieldset.$element, this.resultContainer.$element);
};
GetterDialog.prototype.silentScan = async function() {
if (window.GetterConfig.enableSilentScan === false || !/(?:list of|surname|given name)/i.test(mw.config.get('wgTitle'))) return;
$('.mw-notification[data-notification-tag="getter-discovery"]').remove();
await this.ensureFieldsInitialized();
const best = this.getBestAnchor();
if (!best) return;
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), [], await this.getExistingLinks(), null);
if (!resultsMap.size) return;
const confirmed = await this.processEntityBatch(Array.from(resultsMap.values()).map(p => p.pageid).slice(0, 50), null);
if (confirmed.size > 0) {
this.cachedResults = confirmed;
const $link = $('<a>', { text: 'Review', href: '#', style: 'font-weight:bold; cursor:pointer' });
$link.on('click', (e) => {
e.preventDefault();
$link.closest('.mw-notification').remove();
this.windowManager.openWindow(this);
this.renderResults(this.cachedResults);
});
mw.notify($('<span>').text(`Getter found ${resultsMap.size > 50 ? '50+' : confirmed.size} potential entries. `).append($link), { tag: 'getter-discovery', autoHide: false });
}
};
GetterDialog.prototype.tokenizeTitle = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return {
given: parts[0] || '',
surname: parts.length > 1 ? parts[parts.length - 1] : '',
middle: parts.length > 2 ? parts.slice(1, -1) : []
};
};
// --- Initialization ---
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui', 'oojs-ui-widgets', 'oojs-ui-windows']).then(() => {
const wm = new OO.ui.WindowManager();
$(document.body).append(wm.$element);
const dialog = new GetterDialog({ size: 'full' });
dialog.windowManager = wm;
wm.addWindows([dialog]);
$(mw.util.addPortletLink('p-tb', '#', 'Getter', 't-getter')).on('click', (e) => {
e.preventDefault();
wm.openWindow(dialog);
});
});
}(mediaWiki, jQuery, OO));
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "0.5",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 10,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
/* global ComradeLib, mw, $ */
(async function() {
try {
await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-options.js&action=raw&ctype=text/javascript');
} catch (e) {
console.warn("Comrade: Could not load /comrade-options.js, using fallback defaults.", e);
}
window.ComradeOptions = Object.assign({
version: "0.5.0",
excludedTemplates: [],
nudgeTimeout: 10000,
debugMode: false
}, window.ComradeOptions || {});
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 res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qid}&props=claims|descriptions|labels&languages=en&format=json&origin=*`).then(r => r.json());
return res.entities[qid];
}
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;
ComradeLib.checkLeadCapitalization(currentTitle);
// 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);
await ComradeLib.checkNoShortDesc(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 isRedirect = !!mw.config.get("wgIsRedirect");
const userExclusions = window.comradeQualityExclusions || [];
const allExclusions = [...window.ComradeOptions.excludedTemplates, ...userExclusions];
const exclusionRegexString = allExclusions.map(t => t.replace("Template:", "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
const exclusionRegex = new RegExp(exclusionRegexString, 'i');
if (isRedirect || exclusionRegex.test(wikitext)) {
console.log("Comrade check skipped: Page is a redirect or excluded via options.");
return;
}
ComradeLib.checkQualityConsistency();
} catch (err) {
console.error("Comrade error:", err);
}
}
init();
})();
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Search') {
return;
}
mw.loader.using(['mediawiki.util']).then(function() {
$(function() {
const $resultsInfo = $('.results-info');
const totalResultsRaw = $resultsInfo.attr('data-mw-num-results-total') || '0';
function formatWithCommas(numberString) {
return numberString.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
const state = {
language: mw.config.get('wgContentLanguage'),
totalResultsFormatted: formatWithCommas(totalResultsRaw)
};
let titlesCache = null;
function extractResultTitles() {
const currentResults = $('.mw-search-result-heading');
if (!titlesCache || titlesCache.length !== currentResults.length) {
titlesCache = currentResults.map(function() {
return $(this).find('a:first').attr('title');
}).get().filter(Boolean);
}
return titlesCache;
}
mw.hook("wikipage.content").add(function() {
titlesCache = null;
});
function addLimitOptions() {
const selects = document.querySelectorAll('.cdx-table-pager__limit-form select[name="limit"]');
if (!selects.length) return;
const valuesToAdd = ["1000", "2500", "5000"];
selects.forEach(function(select) {
valuesToAdd.forEach(function(value) {
if (select.querySelector('option[value="' + value + '"]')) return;
const option = document.createElement("option");
option.value = value;
option.textContent = formatWithCommas(value) + " items";
const existingOptions = Array.from(select.querySelectorAll("option"));
const insertBefore = existingOptions.find(function(opt) {
return Number(opt.value) > Number(value);
});
if (insertBefore) {
select.insertBefore(option, insertBefore);
} else {
select.appendChild(option);
}
});
});
}
mw.hook("wikipage.content").add(addLimitOptions);
const numLinks = $('.mw-numlink');
if (numLinks.length > 4 && !numLinks.eq(4).next().is('a[href*="limit=5000"]')) {
const $firstAnchor = numLinks.eq(4);
const $firstSpan = $('<span> | </span>').insertAfter($firstAnchor);
const url1 = new URL(window.location.href);
url1.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url1.toString()
}).insertAfter($firstAnchor).before($firstSpan);
}
if (numLinks.length > 9 && !numLinks.eq(9).next().is('a[href*="limit=5000"]')) {
const $secondAnchor = numLinks.eq(9);
const $secondSpan = $('<span> | </span>').insertAfter($secondAnchor);
const url2 = new URL(window.location.href);
url2.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url2.toString()
}).insertAfter($secondAnchor).before($secondSpan);
}
const $container = $("<div></div>").addClass("enhanced-search-container").hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const interwikiResults = document.getElementById("mw-interwiki-results");
if (interwikiResults) {
$container.addClass("enhanced-search-floating").insertBefore("#mw-interwiki-results");
} else {
$container.addClass("enhanced-search-absolute").insertBefore(".searchresults");
}
const totalFormattedStr = state.totalResultsFormatted;
const $titleElement = $("<div>Show a shortened list of<br />this page's " + $('.mw-search-result-heading').length + " results <small>(of total of " + totalFormattedStr + ")</small></div>").addClass("enhanced-header").appendTo($container);
const $contentArea = $("<div></div>").addClass("enhanced-content-area").appendTo($container).hide();
const $note1 = $("<div>Copy this page\'s " + $('.mw-search-result-heading').length + " results to the clipboard as...</div>").addClass("enhanced-note").insertAfter($container).hide();
function setupHoverTooltip(element, message) {
$(element).attr('title', message).hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
}
const $note2 = $("<div>...plain text<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note1).hide();
setupHoverTooltip($note2, 'This button copies this page\'s results to the clipboard as plain text, i.e. without wikitext markup. Since this copies only the results shown on this search page, make sure you have clicked the "view 20, 50, 100, 250, 500, 5000" link. This format is compatible with PetScan and AutoWikiBrowser (AWB) for example.');
const $note3 = $("<div>...wikitext<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note2).hide();
setupHoverTooltip($note3, 'This button copies this page\'s results to the clipboard and also adds wikitext markup.');
const $note4 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note3).hide();
setupHoverTooltip($note4, 'This button copies a PetScan URL with this page\'s results to the clipboard. Note that if the PetScan URL becomes too long (~250+ pages), the URL might not work.');
const $note5 = $("<div>...or this search query's total results of " + totalFormattedStr + "<sup> (?)</sup> as...</div>").addClass("enhanced-note").insertAfter($note4).hide().attr('title', 'Note that the actual limit is 10,000, which is MediaWiki\'s maximum limit.').hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const $note6 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note5).hide();
setupHoverTooltip($note6, 'This button copies a PetScan URL with all ' + totalFormattedStr + ' results to the clipboard. Note that the maximum limit is 10,000 due to MediaWiki\'s restriction.');
const $note7 = $("<div><sup>(?)</sup> = hover over for tooltip details</div>").addClass("enhanced-note-right").insertAfter($note6).hide();
const noteElements = [$note1, $note2, $note3, $note4, $note5, $note6, $note7];
if (interwikiResults !== null) {
noteElements.forEach($el => $el.addClass("enhanced-floating-elements"));
} else {
noteElements.forEach(function($el, index) {
$el.addClass("enhanced-absolute-elements enhanced-position-" + index);
});
}
function formatPlain() {
return extractResultTitles().join("\r\n");
}
function formatWikitext() {
const titles = extractResultTitles().map(title => "* [[" + title + "]]");
const header = "This is a list of [" + window.location.href + " these search results] (made with the script [[User:JAAqqO/EnhancedSearchPage|EnhancedSearchPage]]):\r\n";
return header + titles.join("\r\n");
}
function formatPetScan() {
const encodedTitles = extractResultTitles().map(encodeURIComponent).join("%0A");
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&manual_list=" + encodedTitles + "&manual_list_wiki=" + state.language + "wiki&project=wikipedia&language=" + state.language + "&doit=";
}
function formatPetScanTotal() {
const oouiInput = document.getElementById("ooui-php-1");
const value = oouiInput ? oouiInput.value : "";
if (value) {
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&ns%5B0%5D=1&search_query=" + encodeURIComponent(value) + "&search_wiki=" + state.language + "wiki&search_max_results=10000&project=wikipedia&language=" + state.language + "&doit=";
}
return "";
}
function copyToClipboard(text) {
if (!text) return;
if (!navigator.clipboard) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
} catch (e) {
console.error('Fallback copy failed', e);
}
textarea.remove();
} else {
navigator.clipboard.writeText(text).catch(function(err) {
console.error("Copy failed: ", err);
});
}
}
function showCopiedMessage(event) {
const posX = event.pageX;
const posY = event.pageY;
const $msg = $("<div class='copied-message'>Copied!</div>").appendTo("body");
$msg.css({
position: "absolute",
top: (posY + 10) + "px",
left: (posX + 10) + "px",
backgroundColor: "#f0f0f0",
border: "1px solid grey",
padding: "5px 10px",
borderRadius: "5px",
zIndex: 9999
});
setTimeout(function() {
$msg.fadeOut("slow", function() {
$(this).remove();
});
}, 500);
}
function performButtonAnimation($element) {
$element.css("transform", "scaleY(0.9)").animate({
opacity: '0.5'
}, "fast", function() {
$(this).animate({
opacity: '1'
}, "slow").css("transform", "scaleY(1)");
});
}
let isExpanded = false;
function showContentArea() {
if (isExpanded) return;
isExpanded = true;
const currentResults = $('.mw-search-result-heading');
$contentArea.empty().append(currentResults.clone());
$contentArea.slideDown();
$(".searchalttitle").remove();
$container.addClass("enhanced-active-container");
$titleElement.remove();
noteElements.forEach($el => $el.show());
}
function bindCopyAction($element, formatterFn) {
$element.click(function(event) {
copyToClipboard(formatterFn());
showCopiedMessage(event);
performButtonAnimation($(this));
});
}
$container.click(showContentArea);
bindCopyAction($note2, formatPlain);
bindCopyAction($note3, formatWikitext);
bindCopyAction($note4, formatPetScan);
bindCopyAction($note6, formatPetScanTotal);
const $pageDown = $("<div><br /><br />[ ↓ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the bottom of the page');
}, function() {
$(this).removeClass("hover");
}).insertBefore('.mw-search-result:eq(0)');
const $pageUp = $("<div>[ ↑ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the top of the page');
}, function() {
$(this).removeClass("hover");
}).insertAfter('.mw-search-result:eq(-1)');
$pageDown.click(function(event) {
event.preventDefault();
$('html, body').animate({
scrollTop: $(document).height()
}, 500);
});
$pageUp.click(function() {
$('html, body').animate({
scrollTop: 0
}, 500);
});
$("<span> (<b>" + $('.mw-search-result-heading').length + "</b> shown on the page)</span>").appendTo('.results-info');
const searchDidYouMean = document.querySelector('.searchdidyoumean');
if (searchDidYouMean) {
searchDidYouMean.style.color = '#b32424';
searchDidYouMean.style.fontSize = '20px';
}
});
});
})();
//</nowiki>
lhcz5dilzhrnej3k0meswu8m44s0i2b
769929
769914
2026-10-24T10:11:58Z
Sam Sailor
26820
Test
769929
javascript
text/javascript
//<nowiki>
(function(mw, $) {
'use strict';
if (mw.config.get('wgNamespaceNumber') !== 0) return;
const UnisexGenderAppender = {
init() {
// Check if page belongs to Category:Unisex given names or contains the template
const categories = mw.config.get('wgCategories') || [];
const isUnisexPage = categories.some(c => c.includes('Unisex given names')) || $('.mw-parser-output').text().includes('unisex given name');
if (!isUnisexPage) return;
// Add portlet button to p-cactions
const link = mw.util.addPortletLink('p-cactions', '#', 'Append gender', 'ca-unisex-gender', 'Fetch and append gender symbols from Wikidata');
if (link) {
$(link).on('click', (e) => {
e.preventDefault();
$(link).off('click').find('a').text('Appending genders...');
this.run();
});
}
},
// Collect all article titles linked in lists/bullet points on the page
async run() {
const titleMap = new Map(); // title -> $element
const titles = [];
$('#mw-content-text li a[title]').each(function() {
const $a = $(this);
const title = $a.attr('title');
// Skip red links, help pages, or main namespaces other than 0
if ($a.hasClass('new') || title.includes(':')) return;
if (!titleMap.has(title)) {
titleMap.set(title, $a);
titles.push(title);
}
});
if (titles.length === 0) {
mw.notify('No valid article links found to process.', {
type: 'warn'
});
return;
}
const api = new mw.Api();
// Batch query titles in chunks of 50 to get their wikibase item (QID)
for (let i = 0; i < titles.length; i += 50) {
const chunk = titles.slice(i, i + 50);
try {
const data = await api.get({
action: 'query',
titles: chunk.join('|'),
prop: 'pageprops',
redirects: true,
format: 'json'
});
const query = data.query || {};
const pages = query.pages || {};
const redirectMap = {};
// Build a map if any redirects occurred
if (query.redirects) {
query.redirects.forEach(r => {
redirectMap[r.from] = r.to;
});
}
const qidMap = {};
const qidList = [];
Object.values(pages).forEach(p => {
// Ignore missing pages (negative page IDs)
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p.title;
}
});
if (!qidList.length) continue;
// Fetch P21 from Wikidata API
const wdRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qidList.join('|')}&props=claims&format=json&origin=*`);
const wdData = await wdRes.json();
const entities = wdData.entities || {};
for (const qid in entities) {
const canonicalTitle = qidMap[qid];
// Find all original DOM elements that targeted either the canonical title OR redirected to it
const matchingOriginalTitles = [canonicalTitle];
for (const [fromTitle, toTitle] of Object.entries(redirectMap)) {
if (toTitle === canonicalTitle) {
matchingOriginalTitles.push(fromTitle);
}
}
// Append symbol right after the link
matchingOriginalTitles.forEach(origTitle => {
const $a = titleMap.get(origTitle);
if (!$a) return;
const claims = entities[qid].claims || {};
const p21 = claims.P21?.[0]?.mainsnak?.datavalue?.value?.id;
let symbol = ' ?';
let titleAttr = 'Gender: Unknown or ambiguous';
if (p21 === 'Q6581097') {
symbol = ' ♂';
titleAttr = 'Gender: Male';
} else if (p21 === 'Q6581072') {
symbol = ' ♀';
titleAttr = 'Gender: Female';
}
// Avoid appending twice if multiple variants match
if ($a.next('.gender-symbol').length === 0) {
$('<span class="gender-symbol" style="font-size: 0.9em; margin-left: 4px; cursor: default; user-select: none; -webkit-user-select: none; -moz-user-select: none;" title="' + titleAttr + '"></span>').text(symbol).insertAfter($a);
}
});
}
} catch (e) {
console.error('[UnisexGenderAppender] Error fetching batch:', e);
}
}
mw.notify('Gender symbols successfully appended!', {
type: 'success'
});
$('#ca-unisex-gender a').text('Genders appended');
}
};
$(() => UnisexGenderAppender.init());
})(mediaWiki, jQuery);
(function(mw, $, OO) {
'use strict';
window.getterWindowManager = null;
window.GetterConfig = Object.assign({
enableSilentScan: true,
fontSize: '12px'
}, window.GetterConfig || {});
const IGNORE_SUFFIXES = ['Sr.', 'Sr', 'Jr.', 'Jr', 'II', 'III', 'IV', 'V', 'MD', 'PhD'];
const LANG_PREFS = ['frwiki', 'dewiki', 'eswiki', 'itwiki', 'nlwiki', 'svwiki'];
const NON_LANG_WIKIS = ['commonswiki', 'specieswiki', 'metawiki', 'mediawikiwiki', 'wikidatawiki', 'wikimaniawiki'];
const suffixRE = new RegExp('\\s(' + IGNORE_SUFFIXES.join('|').replace(/\./g, '\\.') + ')$', 'i');
const PREVIEW_ERROR_HTML = '<em style="color:#b32424">Preview unavailable</em>';
// --- Constructors ---
function GetterDialog(config) {
GetterDialog.super.call(this, config);
this.abortController = null;
this.api = new mw.Api();
this.fieldsInitPromise = null;
this.pageTextPromise = null;
this.cachedResults = null;
this.items = [];
this.previewCache = {};
this.previewElements = new Map();
this.previewPending = new Set();
this.previewGeneration = 0;
this.setupUI();
}
OO.inheritClass(GetterDialog, OO.ui.ProcessDialog);
GetterDialog.static.name = 'getterDialog';
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
// --- Static Definitions ---
GetterDialog.static.name = 'getterDialog';
GetterDialog.static.title = 'Getter: Wikipedia List Builder';
GetterDialog.static.actions = [{ action: 'generate', label: 'Generate List', flags: ['primary', 'progressive'], modes: 'edit' }, { action: 'back', label: 'Go back', modes: 'result' }, { action: 'exit', label: 'Exit', flags: 'safe', modes: ['edit', 'result'] }];
// --- GetterDialog Prototype Methods (Alphabetical) ---
GetterDialog.prototype.autoPopulateFields = async function() {
const title = mw.config.get('wgTitle'),
clean = this.cleanTitle(title);
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const given = text.match(/\{\{\s*given name\b[^}]*\}\}/i),
surname = /\{\{\s*surname\s*(?:\||\}\})/i.test(text);
if (surname || (given && /\|\s*type\s*=\s*both\b/i.test(given[0]))) this.surnameTags.addTag(clean);
if (given) this.givenTags.addTag(clean);
if (!surname && !given) this.givenTags.addTag(clean);
} catch (e) { this.givenTags.addTag(clean); }
};
GetterDialog.prototype.batchFetchPreviews = async function(items) {
const generation = this.previewGeneration;
for (let i = 0; i < items.length; i += 50) {
const chunk = items.slice(i, i + 50);
chunk.forEach(item => this.previewPending.add(item.page.title));
try {
const text = chunk.map(item => `<div data-getter-preview="${mw.html.escape(item.page.title)}">{{anbl|${item.page.title}}}</div>`).join('\n');
const data = await this.api.post({ action: 'parse', text: text, contentmodel: 'wikitext', prop: 'text' });
if (generation !== this.previewGeneration) return;
const $results = $(data.parse.text['*']).find('[data-getter-preview]');
$results.each((_, el) => {
const title = el.dataset.getterPreview;
$(el).find('a').attr('target', '_blank').attr('rel', 'noopener');
const html = el.innerHTML;
if (title && this.previewPending.has(title)) {
this.previewCache[title] = html;
const targetEl = this.previewElements.get(title);
if (targetEl) targetEl.innerHTML = html;
}
});
} catch (e) {
console.error('[Getter] Batch parse error:', e);
if (generation === this.previewGeneration) this.setPreviewError(chunk);
} finally {
chunk.forEach(item => this.previewPending.delete(item.page.title));
}
}
};
GetterDialog.prototype.cleanTitle = (t) => t.replace(suffixRE, '').replace(/\s?\([^)]*\)$/, '').trim();
GetterDialog.prototype.compileCriteria = (vals) => vals.map(v => v.trim()).filter(Boolean).map(v => new RegExp('^' + v.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\?/g, '.').replace(/\*/g, '.*') + '$', 'i'));
GetterDialog.prototype.copyToClipboard = async function(text) {
if (window.isSecureContext && navigator.clipboard?.writeText) {
try { await navigator.clipboard.writeText(text); return true; } catch { /* empty */ }
}
const $ta = $('<textarea>').val(text).css({ position: 'fixed', top: '-1000px', left: '-1000px' }).appendTo(document.body);
const el = $ta[0];
el.focus();
el.select();
const success = document.execCommand('copy');
$ta.remove();
return success;
};
GetterDialog.prototype.createButtonRow = function(colType) {
const $row = $('<div>').css({ display: 'flex', 'flex-wrap': 'wrap', gap: '5px', 'margin-top': '10px' });
const performCopy = async (shouldExit) => {
console.log('performCopy triggered for:', colType);
if (!this.level || !this.mode) {
mw.notify('Formatting widgets not ready yet.', { type: 'error' });
return;
}
const selectedItems = (colType === 'list') ? this.items.filter(i => i.status !== null) : this.items;
if (selectedItems.length === 0) {
mw.notify('No items to copy.', { type: 'warn' });
return;
}
try {
const level = this.level.getValue();
const mode = this.mode.getValue();
const getText = (i) => {
if (colType === 'list') return i.status === 'wikitext' ? i.wikitext : i.anbl;
if (colType === 'wikitext') return i.wikitext;
return i.anbl;
};
const formatted = this.formatListWithHeadings(selectedItems, level, mode, getText);
const success = await this.copyToClipboard(formatted);
if (success) {
mw.notify(`Copied ${selectedItems.length} items!`, { type: 'success' });
if (shouldExit) this.close();
}
} catch (e) {
console.error('Copy error:', e);
mw.notify('Failed to copy list.', { type: 'error' });
}
};
const copyBtn = new OO.ui.ButtonWidget({ label: 'Copy' });
copyBtn.on('click', () => performCopy(false));
$row.append(copyBtn.$element);
const exitBtn = new OO.ui.ButtonWidget({ label: 'Copy & Exit', flags: ['progressive'] });
exitBtn.on('click', () => performCopy(true));
$row.append(exitBtn.$element);
if (colType === 'wikitext' || colType === 'anbl') {
const toggleBtn = new OO.ui.ButtonWidget({ label: 'Toggle All' });
toggleBtn.on('click', () => {
const allSet = this.items.every(i => i.status === colType);
this.items.forEach(i => i.status = allSet ? null : colType);
this.refreshUI();
});
const smartBtn = new OO.ui.ButtonWidget({ label: 'Smart Add' });
smartBtn.on('click', () => {
this.items.forEach(i => { if (!i.status) i.status = colType; });
this.refreshUI();
});
$row.append(toggleBtn.$element, smartBtn.$element);
} else if (colType === 'list') {
const clearBtn = new OO.ui.ButtonWidget({ label: 'Clear', flags: ['destructive'] });
clearBtn.on('click', () => {
this.items.forEach(i => i.status = null);
this.refreshUI();
});
$row.append(clearBtn.$element);
}
return $row;
};
GetterDialog.prototype.ensureFieldsInitialized = function() {
if (!this.fieldsInitPromise) {
this.fieldsInitPromise = (async () => {
this.givenTags.clearItems();
this.middleTags.clearItems();
this.surnameTags.clearItems();
await this.autoPopulateFields();
})();
}
return this.fieldsInitPromise;
};
GetterDialog.prototype.fetchEntities = async function(qids, signal) {
const all = {};
for (let i = 0; i < qids.length; i += 50) {
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');
const res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qids.slice(i, i + 50).join('|')}&props=claims|labels|descriptions|sitelinks&languages=en&format=json&origin=*`, { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
Object.assign(all, data.entities || {});
}
return all;
};
GetterDialog.prototype.findCandidates = async function(bestAnchor, compiled, excludeList, existing, signal) {
const resultsMap = new Map();
const term = bestAnchor.term.replace(/"/g, '\\"').trim();
const isWildcard = /[*?]/.test(term);
const DEBUG = true;
let searches = [];
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
searches.push(`intitle:${term}`);
} else if (isWildcard) {
searches = [`intitle:${term.replace(/\?/g, '*')}`, `intitle:"${term.replace(/[*?]/g, '')}"`];
} else {
searches = [`intitle:"${term}"`];
}
const MAX_CONTINUATIONS = 20;
const runSearch = async (srsearch) => {
let cont = {};
let continuations = 0;
let fetched = 0;
try {
do {
const data = await this.api.get({
action: 'query',
list: 'search',
srsearch,
srlimit: 'max',
sroffset: cont.sroffset || 0,
format: 'json'
}, { signal });
const results = data.query?.search || [];
const aarons = results.filter(r => r.title.startsWith('Aaron')).map(r => r.title);
if (aarons.length) {
console.log(`[${srsearch}] offset=${cont.sroffset || 0}`, aarons);
}
for (const p of results) {
const title = p.title.replace(/_/g, ' ').trim();
if (title.startsWith('Aaron')) {
console.log('Checking', title);
console.log({
existing: existing.has(title),
alreadyFound: resultsMap.has(title),
excluded: excludeList.some(e => title.toLowerCase().includes(e)),
matches: this.matchesCriteria(title, compiled)
});
}
if (existing.has(title) || resultsMap.has(title)) continue;
if (excludeList.some(e => title.toLowerCase().includes(e))) continue;
if (this.matchesCriteria(title, compiled)) {
resultsMap.set(title, {
title,
pageid: p.pageid
});
}
if (title.includes('Rahsaan')) {
console.log('Found Rahsaan', title);
}
}
cont = data.continue || {};
continuations++;
} while (cont.sroffset && continuations < MAX_CONTINUATIONS && !signal?.aborted);
} catch (e) {
if (e.name === 'AbortError') {
throw e;
}
console.error(`[Getter] Search error for "${srsearch}":`, e);
}
if (DEBUG) {
console.log(`[Getter] "${srsearch}" → ${fetched} hits, ${resultsMap.size} candidates`);
}
};
for (const srsearch of searches) {
if (signal?.aborted) {
break;
}
await runSearch(srsearch);
}
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
if (DEBUG) {
console.log(`[Getter] Starting A–Z boost for "${term}"`);
}
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (const letter of alphabet) {
if (signal?.aborted) {
break;
}
await runSearch(`intitle:${letter} intitle:${term}`);
}
}
if (DEBUG) {
console.log(`[Getter] Final candidates for "${term}": ${resultsMap.size}`);
}
return resultsMap;
};
GetterDialog.prototype.formatListWithHeadings = function(items, level, mode, getText) {
if (mode === 'none') return items.map(getText).join('\n');
if (mode === 'smart' && items.length > 0) {
const firstChar = items[0].sortKey.charAt(0).toUpperCase();
if (items.every(i => i.sortKey.charAt(0).toUpperCase() === firstChar)) {
return items.map(getText).join('\n');
}
}
const prefix = '='.repeat(level);
let segments = [];
if (mode === 'alpha') {
const groups = {};
items.forEach(i => {
const char = i.sortKey.charAt(0).toUpperCase();
if (!groups[char]) groups[char] = [];
groups[char].push(i);
});
Object.keys(groups).sort().forEach(char => segments.push({ title: char, items: groups[char] }));
} else {
const count = items.length;
const num = count > 200 ? 26 : (count > 150 ? 3 : 2);
const chunkSize = Math.floor(count / num);
let start = 0;
for (let i = 0; i < num && start < count; i++) {
let end = (i === num - 1) ? count : start + chunkSize;
while (end < count && items[end - 1].sortKey.charAt(0) === items[end].sortKey.charAt(0)) end++;
const chunk = items.slice(start, end);
if (chunk.length) {
const title = this.getSmartHeading(chunk[0], chunk[chunk.length - 1]);
segments.push({ title, items: chunk });
}
start = end;
}
}
return segments.map(s => `${prefix} ${s.title} ${prefix}\n${s.items.map(getText).join('\n')}`).join('\n\n');
};
GetterDialog.prototype.formatResult = function(p, mode, includeIll) {
if (mode === 'anbl') return `* {{anbl|${p.title}}}`;
const bY = p.ent?.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const dY = p.ent?.claims?.P570?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const bio = bY ? (dY ? ` (${bY}–${dY})` : ` (born ${bY})`) : '';
const rawDesc = (p.desc || '').replace(/\s?\(\s*(?:born|d\.)?\s*\d{4}(?:\s*–\s*\d{4})?\s*\)$/i, '').trim();
const suffix = rawDesc ? `, ${rawDesc}` : '';
let display = `[[${p.title}]]`;
if (includeIll) {
const ill = this.getPreferredSitelink(p.ent);
if (ill) display = `{{ill|${p.title}|${ill.lang}|${ill.title}}}`;
}
return `* ${display}${bio}${suffix}`;
};
GetterDialog.prototype.getActionProcess = function(action) {
if (action === 'generate') return new OO.ui.Process(async () => { await this.runGetter(); });
if (action === 'back') {
if (this.abortController) this.abortController.abort();
return new OO.ui.Process(() => {
this.resultContainer.$element.hide();
this.fieldset.$element.show();
this.actions.setMode('edit');
});
}
if (action === 'exit') { if (this.abortController) this.abortController.abort(); return new OO.ui.Process(() => this.close()); }
return GetterDialog.super.prototype.getActionProcess.call(this, action);
};
GetterDialog.prototype.getBestAnchor = function() {
const getValues = (w) => w.getValue().map(t => t.trim()).filter(Boolean);
const candidates = [...getValues(this.surnameTags).map(t => ({ term: t, field: 'surname', weight: 2000 + this.score(t) })), ...getValues(this.givenTags).map(t => ({ term: t, field: 'given', weight: 1000 + this.score(t) })), ...getValues(this.middleTags).map(t => ({ term: t, field: 'middle', weight: 0 + this.score(t) }))];
return candidates.length === 0 ? null : candidates.reduce((b, c) => (c.weight > b.weight ? c : b));
};
GetterDialog.prototype.getCompiledCriteria = function() { return { given: this.compileCriteria(this.givenTags.getValue()), surname: this.compileCriteria(this.surnameTags.getValue()), middle: this.compileCriteria(this.middleTags.getValue()) }; };
GetterDialog.prototype.getExistingLinks = async function() {
const titles = new Set();
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const regexes = [/\[\[([^\]|#]+)/g, /\{\{(?:anbl|Annotated biography link)\|([^}|]+)/gi];
regexes.forEach(re => { let m; while ((m = re.exec(text)) !== null) titles.add(this.normalizeTitle(m[1])); });
} catch (e) { console.error('[Getter] Parse error:', e); }
return titles;
};
GetterDialog.prototype.getPageText = function() { return this.pageTextPromise ??= this.api.get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'wikitext', format: 'json' }); };
GetterDialog.prototype.getPreferredSitelink = function(ent) {
const sitelinks = ent.sitelinks || {};
const key = LANG_PREFS.find(l => sitelinks[l]) || Object.keys(sitelinks).find(k => k.endsWith('wiki') && k !== 'enwiki' && !NON_LANG_WIKIS.includes(k));
return key ? { lang: key.replace(/wiki$/, ''), title: sitelinks[key].title } : null;
};
GetterDialog.prototype.getSetupProcess = function(data) {
return GetterDialog.super.prototype.getSetupProcess.call(this, data).next(() => {
if (data && data.pageTitle) {
if (this.givenTags.getValue().indexOf(data.pageTitle) === -1) {
this.givenTags.addTag(data.pageTitle);
}
}
this.autoPopulateFields();
});
};
GetterDialog.prototype.getSmartHeading = function(startItem, endItem) {
const char1 = startItem.sortKey.charAt(0).toUpperCase();
const char2 = endItem.sortKey.charAt(0).toUpperCase();
return char1 === char2 ? char1 : `${char1}–${char2}`;
};
GetterDialog.prototype.getSortKey = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return parts.length <= 1 ? clean.toLowerCase() : (parts[parts.length - 1] + ', ' + parts.slice(0, -1).join(' ') + ' | ' + clean).toLowerCase();
};
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
GetterDialog.prototype.matchesCriteria = function(title, compiled) {
const parts = this.tokenizeTitle(title);
const check = (val, regs) => regs.length === 0 || regs.some(re => re.test(val));
let givenMatch = check(parts.given, compiled.given);
if (!givenMatch && compiled.given.length > 0) {
const allTokens = this.cleanTitle(title).toLowerCase().split(/\s+/);
givenMatch = compiled.given.some(re => allTokens.some(token => re.test(token)));
}
const surnameMatch = check(parts.surname, compiled.surname);
const middleMatch = compiled.middle.length === 0 || compiled.middle.some(re => parts.middle.some(t => re.test(t)));
if (title.startsWith('Aaron')) {
console.log({
title,
parts,
givenMatch,
surnameMatch,
middleMatch,
compiled
});
}
return givenMatch && surnameMatch && middleMatch;
};
GetterDialog.prototype.normalizeTitle = (t) => { const title = mw.Title.newFromText(t); return title ? title.getPrefixedText() : t.replace(/_/g, ' ').trim(); };
GetterDialog.prototype.processEntityBatch = async function(pageIds, signal) {
const confirmed = new Map();
const pD = await this.api.get({ action: 'query', prop: 'pageprops|description', pageids: pageIds.join('|'), format: 'json' }, { signal });
const qidMap = {},
qidList = [];
Object.values(pD.query.pages).forEach(p => {
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p;
}
});
if (!qidList.length) return confirmed;
const entities = await this.fetchEntities(qidList, signal);
for (const qid in entities) {
const ent = entities[qid],
p = qidMap[qid],
isH = ent.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
if ((this.humanOnly.isSelected() && !isH) || (this.excludeHumans.isSelected() && isH)) continue;
confirmed.set(p.title, { title: p.title, desc: p.description || ent.descriptions?.en?.value || '', ent });
}
return confirmed;
};
GetterDialog.prototype.refreshPreviews = function() {
this.previewCache = {};
this.previewPending.clear();
this.previewGeneration++;
this.refreshUI();
};
GetterDialog.prototype.refreshUI = function() {
this.col1.empty();
this.col2.empty();
this.col3.empty();
this.col4.empty();
this.previewElements.clear();
const $ul = $('<ul>').css({ 'list-style-type': 'disc', 'padding-left': '20px', margin: '0' });
this.col4.append($ul);
const fSize = window.GetterConfig.fontSize;
const itemStyle = {
cursor: 'pointer',
padding: '2px',
'font-size': fSize,
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
};
this.items.forEach(item => {
const $w = $('<div>').text(item.wikitext).attr('title', item.wikitext).css({ ...itemStyle, opacity: item.status === 'wikitext' ? 0.3 : 1 });
$w.on('click', () => {
item.status = (item.status === 'wikitext' ? null : 'wikitext');
this.refreshUI();
});
this.col1.append($w);
const $a = $('<div>').text(item.anbl).attr('title', item.anbl).css({ ...itemStyle, opacity: item.status === 'anbl' ? 0.3 : 1 });
$a.on('click', () => {
item.status = (item.status === 'anbl' ? null : 'anbl');
this.refreshUI();
});
this.col3.append($a);
const $li = $('<li>').attr('title', item.page.title).css({
'font-size': fSize,
'margin-bottom': '2px',
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
}).html(this.previewCache[item.page.title] || '<em>Loading...</em>');
$ul.append($li);
this.previewElements.set(item.page.title, $li[0]);
});
this.items.filter(i => i.status).forEach(item => {
const text = item.status === 'wikitext' ? item.wikitext : item.anbl;
this.col2.append($('<div>').text(text).attr('title', text).css({ 'margin-bottom': '4px', 'font-size': fSize }));
});
const missing = this.items.filter(i => !(i.page.title in this.previewCache) && !this.previewPending.has(i.page.title));
if (missing.length) this.batchFetchPreviews(missing);
};
GetterDialog.prototype.renderResults = async function(confirmed) {
this.previewGeneration++;
this.previewPending.clear();
this.items = Array.from(confirmed.values()).map(p => ({
page: p,
sortKey: this.sortBySurname.isSelected() ? this.getSortKey(p.title) : this.cleanTitle(p.title).toLowerCase(),
wikitext: this.formatResult(p, 'wiki', this.includeIll.isSelected()),
anbl: this.formatResult(p, 'anbl', this.includeIll.isSelected()),
status: null
})).sort((a, b) => a.sortKey.localeCompare(b.sortKey, undefined, { sensitivity: 'base' }));
this.refreshUI();
this.fieldset.$element.hide();
this.resultContainer.$element.show();
this.resultControls.$element.show();
this.actions.setMode('result');
};
GetterDialog.prototype.runGetter = async function() {
this.abortController = new AbortController();
this.cachedResults = null;
try {
const best = this.getBestAnchor();
if (!best) { mw.notify('Please enter search terms.', { type: 'error' }); return; }
const existing = this.updateMode.isSelected() ? await this.getExistingLinks() : new Set();
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), this.excludeTags.getValue().map(e => e.trim().toLowerCase()), existing, this.abortController.signal);
const confirmed = new Map(),
pageIds = Array.from(resultsMap.values()).map(p => p.pageid);
for (let i = 0; i < pageIds.length; i += 50) {
const batchConfirmed = await this.processEntityBatch(pageIds.slice(i, i + 50), this.abortController.signal);
batchConfirmed.forEach((val, key) => confirmed.set(key, val));
}
this.cachedResults = confirmed;
this.renderResults(confirmed);
} catch (e) { if (e.name !== 'AbortError') mw.notify('Error generating list.', { type: 'error' }); } finally { this.abortController = null; }
};
GetterDialog.prototype.score = (term) => { const w = (term.match(/[*?]/g) || []).length; return (w === 0 ? 1000 : 0) + term.replace(/[*?]/g, '').length - w; };
GetterDialog.prototype.setPreviewError = function(items) {
items.forEach(item => {
this.previewCache[item.page.title] ??= PREVIEW_ERROR_HTML;
const targetEl = this.previewElements.get(item.page.title);
if (targetEl) targetEl.innerHTML = this.previewCache[item.page.title];
});
};
GetterDialog.prototype.setupUI = function() {
this.givenTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Given names...', allowArbitrary: true });
this.middleTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Middle terms...', allowArbitrary: true });
this.surnameTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Surnames...', allowArbitrary: true });
this.excludeTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Exclude strings...', allowArbitrary: true });
this.humanOnly = new OO.ui.CheckboxInputWidget({ selected: true });
this.excludeHumans = new OO.ui.CheckboxInputWidget({ selected: false });
this.includeIll = new OO.ui.CheckboxInputWidget({ selected: false });
this.sortBySurname = new OO.ui.CheckboxInputWidget({ selected: true });
this.updateMode = new OO.ui.CheckboxInputWidget({ selected: false });
this.mode = new OO.ui.RadioSelectInputWidget({
options: [{ data: 'none', label: 'No headings' }, { data: 'smart', label: 'Smart grouping' }, { data: 'alpha', label: 'A-Z' }],
value: 'none'
});
this.level = new OO.ui.RadioSelectInputWidget({
options: [{ data: 2, label: 'Level 2' }, { data: 3, label: 'Level 3' }, { data: 4, label: 'Level 4' }],
value: 3
});
if (!this.level || !this.mode) {
console.error('Level or mode widget missing during UI setup');
}
mw.util.addCSS(` .getter-result-fieldset .oo-ui-fieldsetLayout-header {
font-size: 13px !important;
color: #444;
}
.getter-formatting-container {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 40px;
}
.getter-formatting-container .oo-ui-fieldLayout {
display: flex;
align-items: center;
margin-top: 2px;
}
.getter-formatting-container .oo-ui-fieldLayout-label {
margin-right: 12px;
font-weight: bold;
width: auto;
flex-shrink: 0;
line-height: 1.4;
}
.oo-ui-radioSelectInputWidget {
vertical-align: middle;
}
`);
this.resultControls = new OO.ui.FieldsetLayout({ label: 'Output Formatting', classes: ['getter-result-fieldset'] });
const $formattingRow = $('<div>').addClass('getter-formatting-container');
const modeLayout = new OO.ui.FieldLayout(this.mode, {
label: 'Grouping Mode',
align: 'top'
});
const levelLayout = new OO.ui.FieldLayout(this.level, {
label: 'Heading Level',
align: 'top'
});
$formattingRow.append(modeLayout.$element, levelLayout.$element);
this.resultControls.$element.append($formattingRow);
this.fieldset = new OO.ui.FieldsetLayout({ label: 'Advanced Filter Settings' });
this.fieldset.addItems([
new OO.ui.FieldLayout(this.givenTags, { label: 'Given names', align: 'top' }),
new OO.ui.FieldLayout(this.middleTags, { label: 'Middle terms', align: 'top' }),
new OO.ui.FieldLayout(this.surnameTags, { label: 'Surnames', align: 'top' }),
new OO.ui.FieldLayout(this.excludeTags, { label: 'Exclude strings', align: 'top' }),
new OO.ui.FieldLayout(this.humanOnly, { label: 'Humans only', align: 'inline' }),
new OO.ui.FieldLayout(this.excludeHumans, { label: 'Exclude humans', align: 'inline' }),
new OO.ui.FieldLayout(this.sortBySurname, { label: 'Sort by surname', align: 'inline' }),
new OO.ui.FieldLayout(this.updateMode, { label: 'Exclude existing links', align: 'inline' }),
new OO.ui.FieldLayout(this.includeIll, { label: 'Include {{ill}}', align: 'inline' })
]);
const colStyle = { height: '480px', 'overflow-y': 'auto', 'overflow-x': 'hidden', border: '1px solid #ccc', padding: '10px', 'white-space': 'nowrap', 'text-overflow': 'ellipsis' };
this.col1 = $('<div>').css(colStyle);
this.col2 = $('<div>').css({ ...colStyle, background: '#f9f9f9' });
this.col3 = $('<div>').css(colStyle);
this.col4 = $('<div>').css(colStyle);
const columns = [this.col1, this.col2, this.col3, this.col4];
columns.forEach(col => {
col.on('scroll', (e) => {
const scrollTop = e.target.scrollTop;
columns.forEach(otherCol => { if (otherCol[0] !== e.target && otherCol[0].scrollTop !== scrollTop) otherCol[0].scrollTop = scrollTop; });
});
});
const $refreshBtn = new OO.ui.ButtonWidget({ label: 'Refresh', icon: 'reload', flags: ['progressive'] });
$refreshBtn.on('click', () => this.refreshPreviews());
this.resultContainer = new OO.ui.PanelLayout({ padded: true, expanded: true });
const $columns = $('<div>').css({ display: 'flex', gap: '10px' });
const createHeader = (label) => new OO.ui.LabelWidget({ label: label }).$element.css({ 'font-weight': 'bold', 'font-size': '13px', 'margin-bottom': '5px' });
$columns.append($('<div>').css('flex', '1.2').append(createHeader('Wikitext'), this.col1, this.createButtonRow('wikitext')), $('<div>').css('flex', '1.2').append(createHeader('Chosen List'), this.col2, this.createButtonRow('list')), $('<div>').css('flex', '0.8').append(createHeader('Anbl'), this.col3, this.createButtonRow('anbl')), $('<div>').css('flex', '1.2').append(createHeader('Anbl Preview'), this.col4, $('<div>').css('margin-top', '10px').append($refreshBtn.$element)));
this.resultContainer.$element.append($columns, $('<div>').css('margin-top', '20px').append(this.resultControls.$element)).hide();
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.panel.$element.append(this.fieldset.$element, this.resultContainer.$element);
};
GetterDialog.prototype.silentScan = async function() {
if (window.GetterConfig.enableSilentScan === false || !/(?:list of|surname|given name)/i.test(mw.config.get('wgTitle'))) return;
$('.mw-notification[data-notification-tag="getter-discovery"]').remove();
await this.ensureFieldsInitialized();
const best = this.getBestAnchor();
if (!best) return;
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), [], await this.getExistingLinks(), null);
if (!resultsMap.size) return;
const confirmed = await this.processEntityBatch(Array.from(resultsMap.values()).map(p => p.pageid).slice(0, 50), null);
if (confirmed.size > 0) {
this.cachedResults = confirmed;
const $link = $('<a>', { text: 'Review', href: '#', style: 'font-weight:bold; cursor:pointer' });
$link.on('click', (e) => {
e.preventDefault();
$link.closest('.mw-notification').remove();
this.windowManager.openWindow(this);
this.renderResults(this.cachedResults);
});
mw.notify($('<span>').text(`Getter found ${resultsMap.size > 50 ? '50+' : confirmed.size} potential entries. `).append($link), { tag: 'getter-discovery', autoHide: false });
}
};
GetterDialog.prototype.tokenizeTitle = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return {
given: parts[0] || '',
surname: parts.length > 1 ? parts[parts.length - 1] : '',
middle: parts.length > 2 ? parts.slice(1, -1) : []
};
};
// --- Initialization ---
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui', 'oojs-ui-widgets', 'oojs-ui-windows']).then(() => {
const wm = new OO.ui.WindowManager();
$(document.body).append(wm.$element);
const dialog = new GetterDialog({ size: 'full' });
dialog.windowManager = wm;
wm.addWindows([dialog]);
$(mw.util.addPortletLink('p-tb', '#', 'Getter', 't-getter')).on('click', (e) => {
e.preventDefault();
wm.openWindow(dialog);
});
});
}(mediaWiki, jQuery, OO));
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "0.5",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 10,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
/* global ComradeLib, mw, $ */
(async function() {
try {
await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-options.js&action=raw&ctype=text/javascript');
} catch (e) {
console.warn("Comrade: Could not load /comrade-options.js, using fallback defaults.", e);
}
window.ComradeOptions = Object.assign({
version: "0.5.0",
excludedTemplates: [],
nudgeTimeout: 10000,
debugMode: false
}, window.ComradeOptions || {});
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 res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qid}&props=claims|descriptions|labels&languages=en&format=json&origin=*`).then(r => r.json());
return res.entities[qid];
}
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;
ComradeLib.checkLeadCapitalization(currentTitle);
// 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);
await ComradeLib.checkNoShortDesc(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 isRedirect = !!mw.config.get("wgIsRedirect");
const userExclusions = window.comradeQualityExclusions || [];
const allExclusions = [...window.ComradeOptions.excludedTemplates, ...userExclusions];
const exclusionRegexString = allExclusions.map(t => t.replace("Template:", "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
const exclusionRegex = new RegExp(exclusionRegexString, 'i');
if (isRedirect || exclusionRegex.test(wikitext)) {
console.log("Comrade check skipped: Page is a redirect or excluded via options.");
return;
}
ComradeLib.checkQualityConsistency();
} catch (err) {
console.error("Comrade error:", err);
}
}
init();
})();
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Search') {
return;
}
mw.loader.using(['mediawiki.util']).then(function() {
$(function() {
const $resultsInfo = $('.results-info');
const totalResultsRaw = $resultsInfo.attr('data-mw-num-results-total') || '0';
function formatWithCommas(numberString) {
return numberString.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
const state = {
language: mw.config.get('wgContentLanguage'),
totalResultsFormatted: formatWithCommas(totalResultsRaw)
};
let titlesCache = null;
function extractResultTitles() {
const currentResults = $('.mw-search-result-heading');
if (!titlesCache || titlesCache.length !== currentResults.length) {
titlesCache = currentResults.map(function() {
return $(this).find('a:first').attr('title');
}).get().filter(Boolean);
}
return titlesCache;
}
mw.hook("wikipage.content").add(function() {
titlesCache = null;
});
function addLimitOptions() {
const selects = document.querySelectorAll('.cdx-table-pager__limit-form select[name="limit"]');
if (!selects.length) return;
const valuesToAdd = ["1000", "2500", "5000"];
selects.forEach(function(select) {
valuesToAdd.forEach(function(value) {
if (select.querySelector('option[value="' + value + '"]')) return;
const option = document.createElement("option");
option.value = value;
option.textContent = formatWithCommas(value) + " items";
const existingOptions = Array.from(select.querySelectorAll("option"));
const insertBefore = existingOptions.find(function(opt) {
return Number(opt.value) > Number(value);
});
if (insertBefore) {
select.insertBefore(option, insertBefore);
} else {
select.appendChild(option);
}
});
});
}
mw.hook("wikipage.content").add(addLimitOptions);
const numLinks = $('.mw-numlink');
if (numLinks.length > 4 && !numLinks.eq(4).next().is('a[href*="limit=5000"]')) {
const $firstAnchor = numLinks.eq(4);
const $firstSpan = $('<span> | </span>').insertAfter($firstAnchor);
const url1 = new URL(window.location.href);
url1.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url1.toString()
}).insertAfter($firstAnchor).before($firstSpan);
}
if (numLinks.length > 9 && !numLinks.eq(9).next().is('a[href*="limit=5000"]')) {
const $secondAnchor = numLinks.eq(9);
const $secondSpan = $('<span> | </span>').insertAfter($secondAnchor);
const url2 = new URL(window.location.href);
url2.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url2.toString()
}).insertAfter($secondAnchor).before($secondSpan);
}
const $container = $("<div></div>").addClass("enhanced-search-container").hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const interwikiResults = document.getElementById("mw-interwiki-results");
if (interwikiResults) {
$container.addClass("enhanced-search-floating").insertBefore("#mw-interwiki-results");
} else {
$container.addClass("enhanced-search-absolute").insertBefore(".searchresults");
}
const totalFormattedStr = state.totalResultsFormatted;
const $titleElement = $("<div>Show a shortened list of<br />this page's " + $('.mw-search-result-heading').length + " results <small>(of total of " + totalFormattedStr + ")</small></div>").addClass("enhanced-header").appendTo($container);
const $contentArea = $("<div></div>").addClass("enhanced-content-area").appendTo($container).hide();
const $note1 = $("<div>Copy this page\'s " + $('.mw-search-result-heading').length + " results to the clipboard as...</div>").addClass("enhanced-note").insertAfter($container).hide();
function setupHoverTooltip(element, message) {
$(element).attr('title', message).hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
}
const $note2 = $("<div>...plain text<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note1).hide();
setupHoverTooltip($note2, 'This button copies this page\'s results to the clipboard as plain text, i.e. without wikitext markup. Since this copies only the results shown on this search page, make sure you have clicked the "view 20, 50, 100, 250, 500, 5000" link. This format is compatible with PetScan and AutoWikiBrowser (AWB) for example.');
const $note3 = $("<div>...wikitext<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note2).hide();
setupHoverTooltip($note3, 'This button copies this page\'s results to the clipboard and also adds wikitext markup.');
const $note4 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note3).hide();
setupHoverTooltip($note4, 'This button copies a PetScan URL with this page\'s results to the clipboard. Note that if the PetScan URL becomes too long (~250+ pages), the URL might not work.');
const $note5 = $("<div>...or this search query's total results of " + totalFormattedStr + "<sup> (?)</sup> as...</div>").addClass("enhanced-note").insertAfter($note4).hide().attr('title', 'Note that the actual limit is 10,000, which is MediaWiki\'s maximum limit.').hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const $note6 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note5).hide();
setupHoverTooltip($note6, 'This button copies a PetScan URL with all ' + totalFormattedStr + ' results to the clipboard. Note that the maximum limit is 10,000 due to MediaWiki\'s restriction.');
const $note7 = $("<div><sup>(?)</sup> = hover over for tooltip details</div>").addClass("enhanced-note-right").insertAfter($note6).hide();
const noteElements = [$note1, $note2, $note3, $note4, $note5, $note6, $note7];
if (interwikiResults !== null) {
noteElements.forEach($el => $el.addClass("enhanced-floating-elements"));
} else {
noteElements.forEach(function($el, index) {
$el.addClass("enhanced-absolute-elements enhanced-position-" + index);
});
}
function formatPlain() {
return extractResultTitles().join("\r\n");
}
function formatWikitext() {
const titles = extractResultTitles().map(title => "* [[" + title + "]]");
const header = "This is a list of [" + window.location.href + " these search results] (made with the script [[User:JAAqqO/EnhancedSearchPage|EnhancedSearchPage]]):\r\n";
return header + titles.join("\r\n");
}
function formatPetScan() {
const encodedTitles = extractResultTitles().map(encodeURIComponent).join("%0A");
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&manual_list=" + encodedTitles + "&manual_list_wiki=" + state.language + "wiki&project=wikipedia&language=" + state.language + "&doit=";
}
function formatPetScanTotal() {
const oouiInput = document.getElementById("ooui-php-1");
const value = oouiInput ? oouiInput.value : "";
if (value) {
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&ns%5B0%5D=1&search_query=" + encodeURIComponent(value) + "&search_wiki=" + state.language + "wiki&search_max_results=10000&project=wikipedia&language=" + state.language + "&doit=";
}
return "";
}
function copyToClipboard(text) {
if (!text) return;
if (!navigator.clipboard) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
} catch (e) {
console.error('Fallback copy failed', e);
}
textarea.remove();
} else {
navigator.clipboard.writeText(text).catch(function(err) {
console.error("Copy failed: ", err);
});
}
}
function showCopiedMessage(event) {
const posX = event.pageX;
const posY = event.pageY;
const $msg = $("<div class='copied-message'>Copied!</div>").appendTo("body");
$msg.css({
position: "absolute",
top: (posY + 10) + "px",
left: (posX + 10) + "px",
backgroundColor: "#f0f0f0",
border: "1px solid grey",
padding: "5px 10px",
borderRadius: "5px",
zIndex: 9999
});
setTimeout(function() {
$msg.fadeOut("slow", function() {
$(this).remove();
});
}, 500);
}
function performButtonAnimation($element) {
$element.css("transform", "scaleY(0.9)").animate({
opacity: '0.5'
}, "fast", function() {
$(this).animate({
opacity: '1'
}, "slow").css("transform", "scaleY(1)");
});
}
let isExpanded = false;
function showContentArea() {
if (isExpanded) return;
isExpanded = true;
const currentResults = $('.mw-search-result-heading');
$contentArea.empty().append(currentResults.clone());
$contentArea.slideDown();
$(".searchalttitle").remove();
$container.addClass("enhanced-active-container");
$titleElement.remove();
noteElements.forEach($el => $el.show());
}
function bindCopyAction($element, formatterFn) {
$element.click(function(event) {
copyToClipboard(formatterFn());
showCopiedMessage(event);
performButtonAnimation($(this));
});
}
$container.click(showContentArea);
bindCopyAction($note2, formatPlain);
bindCopyAction($note3, formatWikitext);
bindCopyAction($note4, formatPetScan);
bindCopyAction($note6, formatPetScanTotal);
const $pageDown = $("<div><br /><br />[ ↓ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the bottom of the page');
}, function() {
$(this).removeClass("hover");
}).insertBefore('.mw-search-result:eq(0)');
const $pageUp = $("<div>[ ↑ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the top of the page');
}, function() {
$(this).removeClass("hover");
}).insertAfter('.mw-search-result:eq(-1)');
$pageDown.click(function(event) {
event.preventDefault();
$('html, body').animate({
scrollTop: $(document).height()
}, 500);
});
$pageUp.click(function() {
$('html, body').animate({
scrollTop: 0
}, 500);
});
$("<span> (<b>" + $('.mw-search-result-heading').length + "</b> shown on the page)</span>").appendTo('.results-info');
const searchDidYouMean = document.querySelector('.searchdidyoumean');
if (searchDidYouMean) {
searchDidYouMean.style.color = '#b32424';
searchDidYouMean.style.fontSize = '20px';
}
});
});
})();
//</nowiki>
6onqdm3w7bg400gh55larc8zryj1pyn
769932
769929
2026-10-24T10:24:29Z
Sam Sailor
26820
Test
769932
javascript
text/javascript
//<nowiki>
(function(mw, $) {
'use strict';
if (mw.config.get('wgNamespaceNumber') !== 0) return;
const UnisexGenderAppender = {
init() {
// Check if page belongs to Category:Unisex given names or contains the template
const categories = mw.config.get('wgCategories') || [];
const isUnisexPage = categories.some(c => c.includes('Unisex given names')) || $('.mw-parser-output').text().includes('unisex given name');
if (!isUnisexPage) return;
// Add portlet button to p-cactions
const link = mw.util.addPortletLink('p-cactions', '#', 'Append gender', 'ca-unisex-gender', 'Fetch and append gender symbols from Wikidata');
if (link) {
$(link).on('click', (e) => {
e.preventDefault();
$(link).off('click').find('a').text('Appending genders...');
this.run();
});
}
},
// Collect all article titles linked in lists/bullet points on the page
async run() {
const titleMap = new Map(); // title -> $element
const titles = [];
$('#mw-content-text li a[title]').each(function() {
const $a = $(this);
const title = $a.attr('title');
// Skip red links, help pages, or main namespaces other than 0
if ($a.hasClass('new') || title.includes(':')) return;
if (!titleMap.has(title)) {
titleMap.set(title, $a);
titles.push(title);
}
});
if (titles.length === 0) {
mw.notify('No valid article links found to process.', {
type: 'warn'
});
return;
}
const api = new mw.Api();
// Batch query titles in chunks of 50 to get their wikibase item (QID)
for (let i = 0; i < titles.length; i += 50) {
const chunk = titles.slice(i, i + 50);
try {
const data = await api.get({
action: 'query',
titles: chunk.join('|'),
prop: 'pageprops',
redirects: true,
format: 'json'
});
const query = data.query || {};
const pages = query.pages || {};
const redirectMap = {};
// Build a map if any redirects occurred
if (query.redirects) {
query.redirects.forEach(r => {
redirectMap[r.from] = r.to;
});
}
const qidMap = {};
const qidList = [];
Object.values(pages).forEach(p => {
// Ignore missing pages (negative page IDs)
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p.title;
}
});
if (!qidList.length) continue;
// Fetch P31 and P21 from Wikidata API
const wdRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qidList.join('|')}&props=claims&format=json&origin=*`);
const wdData = await wdRes.json();
const entities = wdData.entities || {};
for (const qid in entities) {
const claims = entities[qid].claims || {};
// Check Property P31 (instance of): must be Q5 (human)
const isHuman = claims.P31?.some(c => c.mainsnak?.datavalue?.value?.id === 'Q5');
if (!isHuman) continue;
const canonicalTitle = qidMap[qid];
// Find all original DOM elements that targeted either the canonical title OR redirected to it
const matchingOriginalTitles = [canonicalTitle];
for (const [fromTitle, toTitle] of Object.entries(redirectMap)) {
if (toTitle === canonicalTitle) {
matchingOriginalTitles.push(fromTitle);
}
}
// Append symbol right after the link
matchingOriginalTitles.forEach(origTitle => {
const $a = titleMap.get(origTitle);
if (!$a) return;
const p21 = claims.P21?.[0]?.mainsnak?.datavalue?.value?.id;
let symbol = ' ?';
let titleAttr = 'Gender: Unknown or ambiguous';
if (p21 === 'Q6581097' || p21 === 'Q2449503') {
symbol = ' ♂';
titleAttr = p21 === 'Q2449503' ? 'Gender: Transgender male' : 'Gender: Male';
} else if (p21 === 'Q6581072' || p21 === 'Q1052281') {
symbol = ' ♀';
titleAttr = p21 === 'Q1052281' ? 'Gender: Transgender female' : 'Gender: Female';
} else if (p21 === 'Q1097630') {
symbol = ' ☿';
titleAttr = 'Gender: Intersex';
}
// Avoid appending twice if multiple variants match
if ($a.next('.gender-symbol').length === 0) {
$('<span class="gender-symbol" style="font-size: 0.9em; margin-left: 4px; cursor: default; user-select: none; -webkit-user-select: none; -moz-user-select: none;" title="' + titleAttr + '"></span>').text(symbol).insertAfter($a);
}
});
}
} catch (e) {
console.error('[UnisexGenderAppender] Error fetching batch:', e);
}
}
mw.notify('Gender symbols successfully appended!', {
type: 'success'
});
$('#ca-unisex-gender a').text('Genders appended');
}
};
$(() => UnisexGenderAppender.init());
})(mediaWiki, jQuery);
(function(mw, $, OO) {
'use strict';
window.getterWindowManager = null;
window.GetterConfig = Object.assign({
enableSilentScan: true,
fontSize: '12px'
}, window.GetterConfig || {});
const IGNORE_SUFFIXES = ['Sr.', 'Sr', 'Jr.', 'Jr', 'II', 'III', 'IV', 'V', 'MD', 'PhD'];
const LANG_PREFS = ['frwiki', 'dewiki', 'eswiki', 'itwiki', 'nlwiki', 'svwiki'];
const NON_LANG_WIKIS = ['commonswiki', 'specieswiki', 'metawiki', 'mediawikiwiki', 'wikidatawiki', 'wikimaniawiki'];
const suffixRE = new RegExp('\\s(' + IGNORE_SUFFIXES.join('|').replace(/\./g, '\\.') + ')$', 'i');
const PREVIEW_ERROR_HTML = '<em style="color:#b32424">Preview unavailable</em>';
// --- Constructors ---
function GetterDialog(config) {
GetterDialog.super.call(this, config);
this.abortController = null;
this.api = new mw.Api();
this.fieldsInitPromise = null;
this.pageTextPromise = null;
this.cachedResults = null;
this.items = [];
this.previewCache = {};
this.previewElements = new Map();
this.previewPending = new Set();
this.previewGeneration = 0;
this.setupUI();
}
OO.inheritClass(GetterDialog, OO.ui.ProcessDialog);
GetterDialog.static.name = 'getterDialog';
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
// --- Static Definitions ---
GetterDialog.static.name = 'getterDialog';
GetterDialog.static.title = 'Getter: Wikipedia List Builder';
GetterDialog.static.actions = [{ action: 'generate', label: 'Generate List', flags: ['primary', 'progressive'], modes: 'edit' }, { action: 'back', label: 'Go back', modes: 'result' }, { action: 'exit', label: 'Exit', flags: 'safe', modes: ['edit', 'result'] }];
// --- GetterDialog Prototype Methods (Alphabetical) ---
GetterDialog.prototype.autoPopulateFields = async function() {
const title = mw.config.get('wgTitle'),
clean = this.cleanTitle(title);
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const given = text.match(/\{\{\s*given name\b[^}]*\}\}/i),
surname = /\{\{\s*surname\s*(?:\||\}\})/i.test(text);
if (surname || (given && /\|\s*type\s*=\s*both\b/i.test(given[0]))) this.surnameTags.addTag(clean);
if (given) this.givenTags.addTag(clean);
if (!surname && !given) this.givenTags.addTag(clean);
} catch (e) { this.givenTags.addTag(clean); }
};
GetterDialog.prototype.batchFetchPreviews = async function(items) {
const generation = this.previewGeneration;
for (let i = 0; i < items.length; i += 50) {
const chunk = items.slice(i, i + 50);
chunk.forEach(item => this.previewPending.add(item.page.title));
try {
const text = chunk.map(item => `<div data-getter-preview="${mw.html.escape(item.page.title)}">{{anbl|${item.page.title}}}</div>`).join('\n');
const data = await this.api.post({ action: 'parse', text: text, contentmodel: 'wikitext', prop: 'text' });
if (generation !== this.previewGeneration) return;
const $results = $(data.parse.text['*']).find('[data-getter-preview]');
$results.each((_, el) => {
const title = el.dataset.getterPreview;
$(el).find('a').attr('target', '_blank').attr('rel', 'noopener');
const html = el.innerHTML;
if (title && this.previewPending.has(title)) {
this.previewCache[title] = html;
const targetEl = this.previewElements.get(title);
if (targetEl) targetEl.innerHTML = html;
}
});
} catch (e) {
console.error('[Getter] Batch parse error:', e);
if (generation === this.previewGeneration) this.setPreviewError(chunk);
} finally {
chunk.forEach(item => this.previewPending.delete(item.page.title));
}
}
};
GetterDialog.prototype.cleanTitle = (t) => t.replace(suffixRE, '').replace(/\s?\([^)]*\)$/, '').trim();
GetterDialog.prototype.compileCriteria = (vals) => vals.map(v => v.trim()).filter(Boolean).map(v => new RegExp('^' + v.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\?/g, '.').replace(/\*/g, '.*') + '$', 'i'));
GetterDialog.prototype.copyToClipboard = async function(text) {
if (window.isSecureContext && navigator.clipboard?.writeText) {
try { await navigator.clipboard.writeText(text); return true; } catch { /* empty */ }
}
const $ta = $('<textarea>').val(text).css({ position: 'fixed', top: '-1000px', left: '-1000px' }).appendTo(document.body);
const el = $ta[0];
el.focus();
el.select();
const success = document.execCommand('copy');
$ta.remove();
return success;
};
GetterDialog.prototype.createButtonRow = function(colType) {
const $row = $('<div>').css({ display: 'flex', 'flex-wrap': 'wrap', gap: '5px', 'margin-top': '10px' });
const performCopy = async (shouldExit) => {
console.log('performCopy triggered for:', colType);
if (!this.level || !this.mode) {
mw.notify('Formatting widgets not ready yet.', { type: 'error' });
return;
}
const selectedItems = (colType === 'list') ? this.items.filter(i => i.status !== null) : this.items;
if (selectedItems.length === 0) {
mw.notify('No items to copy.', { type: 'warn' });
return;
}
try {
const level = this.level.getValue();
const mode = this.mode.getValue();
const getText = (i) => {
if (colType === 'list') return i.status === 'wikitext' ? i.wikitext : i.anbl;
if (colType === 'wikitext') return i.wikitext;
return i.anbl;
};
const formatted = this.formatListWithHeadings(selectedItems, level, mode, getText);
const success = await this.copyToClipboard(formatted);
if (success) {
mw.notify(`Copied ${selectedItems.length} items!`, { type: 'success' });
if (shouldExit) this.close();
}
} catch (e) {
console.error('Copy error:', e);
mw.notify('Failed to copy list.', { type: 'error' });
}
};
const copyBtn = new OO.ui.ButtonWidget({ label: 'Copy' });
copyBtn.on('click', () => performCopy(false));
$row.append(copyBtn.$element);
const exitBtn = new OO.ui.ButtonWidget({ label: 'Copy & Exit', flags: ['progressive'] });
exitBtn.on('click', () => performCopy(true));
$row.append(exitBtn.$element);
if (colType === 'wikitext' || colType === 'anbl') {
const toggleBtn = new OO.ui.ButtonWidget({ label: 'Toggle All' });
toggleBtn.on('click', () => {
const allSet = this.items.every(i => i.status === colType);
this.items.forEach(i => i.status = allSet ? null : colType);
this.refreshUI();
});
const smartBtn = new OO.ui.ButtonWidget({ label: 'Smart Add' });
smartBtn.on('click', () => {
this.items.forEach(i => { if (!i.status) i.status = colType; });
this.refreshUI();
});
$row.append(toggleBtn.$element, smartBtn.$element);
} else if (colType === 'list') {
const clearBtn = new OO.ui.ButtonWidget({ label: 'Clear', flags: ['destructive'] });
clearBtn.on('click', () => {
this.items.forEach(i => i.status = null);
this.refreshUI();
});
$row.append(clearBtn.$element);
}
return $row;
};
GetterDialog.prototype.ensureFieldsInitialized = function() {
if (!this.fieldsInitPromise) {
this.fieldsInitPromise = (async () => {
this.givenTags.clearItems();
this.middleTags.clearItems();
this.surnameTags.clearItems();
await this.autoPopulateFields();
})();
}
return this.fieldsInitPromise;
};
GetterDialog.prototype.fetchEntities = async function(qids, signal) {
const all = {};
for (let i = 0; i < qids.length; i += 50) {
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');
const res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qids.slice(i, i + 50).join('|')}&props=claims|labels|descriptions|sitelinks&languages=en&format=json&origin=*`, { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
Object.assign(all, data.entities || {});
}
return all;
};
GetterDialog.prototype.findCandidates = async function(bestAnchor, compiled, excludeList, existing, signal) {
const resultsMap = new Map();
const term = bestAnchor.term.replace(/"/g, '\\"').trim();
const isWildcard = /[*?]/.test(term);
const DEBUG = true;
let searches = [];
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
searches.push(`intitle:${term}`);
} else if (isWildcard) {
searches = [`intitle:${term.replace(/\?/g, '*')}`, `intitle:"${term.replace(/[*?]/g, '')}"`];
} else {
searches = [`intitle:"${term}"`];
}
const MAX_CONTINUATIONS = 20;
const runSearch = async (srsearch) => {
let cont = {};
let continuations = 0;
let fetched = 0;
try {
do {
const data = await this.api.get({
action: 'query',
list: 'search',
srsearch,
srlimit: 'max',
sroffset: cont.sroffset || 0,
format: 'json'
}, { signal });
const results = data.query?.search || [];
const aarons = results.filter(r => r.title.startsWith('Aaron')).map(r => r.title);
if (aarons.length) {
console.log(`[${srsearch}] offset=${cont.sroffset || 0}`, aarons);
}
for (const p of results) {
const title = p.title.replace(/_/g, ' ').trim();
if (title.startsWith('Aaron')) {
console.log('Checking', title);
console.log({
existing: existing.has(title),
alreadyFound: resultsMap.has(title),
excluded: excludeList.some(e => title.toLowerCase().includes(e)),
matches: this.matchesCriteria(title, compiled)
});
}
if (existing.has(title) || resultsMap.has(title)) continue;
if (excludeList.some(e => title.toLowerCase().includes(e))) continue;
if (this.matchesCriteria(title, compiled)) {
resultsMap.set(title, {
title,
pageid: p.pageid
});
}
if (title.includes('Rahsaan')) {
console.log('Found Rahsaan', title);
}
}
cont = data.continue || {};
continuations++;
} while (cont.sroffset && continuations < MAX_CONTINUATIONS && !signal?.aborted);
} catch (e) {
if (e.name === 'AbortError') {
throw e;
}
console.error(`[Getter] Search error for "${srsearch}":`, e);
}
if (DEBUG) {
console.log(`[Getter] "${srsearch}" → ${fetched} hits, ${resultsMap.size} candidates`);
}
};
for (const srsearch of searches) {
if (signal?.aborted) {
break;
}
await runSearch(srsearch);
}
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
if (DEBUG) {
console.log(`[Getter] Starting A–Z boost for "${term}"`);
}
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (const letter of alphabet) {
if (signal?.aborted) {
break;
}
await runSearch(`intitle:${letter} intitle:${term}`);
}
}
if (DEBUG) {
console.log(`[Getter] Final candidates for "${term}": ${resultsMap.size}`);
}
return resultsMap;
};
GetterDialog.prototype.formatListWithHeadings = function(items, level, mode, getText) {
if (mode === 'none') return items.map(getText).join('\n');
if (mode === 'smart' && items.length > 0) {
const firstChar = items[0].sortKey.charAt(0).toUpperCase();
if (items.every(i => i.sortKey.charAt(0).toUpperCase() === firstChar)) {
return items.map(getText).join('\n');
}
}
const prefix = '='.repeat(level);
let segments = [];
if (mode === 'alpha') {
const groups = {};
items.forEach(i => {
const char = i.sortKey.charAt(0).toUpperCase();
if (!groups[char]) groups[char] = [];
groups[char].push(i);
});
Object.keys(groups).sort().forEach(char => segments.push({ title: char, items: groups[char] }));
} else {
const count = items.length;
const num = count > 200 ? 26 : (count > 150 ? 3 : 2);
const chunkSize = Math.floor(count / num);
let start = 0;
for (let i = 0; i < num && start < count; i++) {
let end = (i === num - 1) ? count : start + chunkSize;
while (end < count && items[end - 1].sortKey.charAt(0) === items[end].sortKey.charAt(0)) end++;
const chunk = items.slice(start, end);
if (chunk.length) {
const title = this.getSmartHeading(chunk[0], chunk[chunk.length - 1]);
segments.push({ title, items: chunk });
}
start = end;
}
}
return segments.map(s => `${prefix} ${s.title} ${prefix}\n${s.items.map(getText).join('\n')}`).join('\n\n');
};
GetterDialog.prototype.formatResult = function(p, mode, includeIll) {
if (mode === 'anbl') return `* {{anbl|${p.title}}}`;
const bY = p.ent?.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const dY = p.ent?.claims?.P570?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const bio = bY ? (dY ? ` (${bY}–${dY})` : ` (born ${bY})`) : '';
const rawDesc = (p.desc || '').replace(/\s?\(\s*(?:born|d\.)?\s*\d{4}(?:\s*–\s*\d{4})?\s*\)$/i, '').trim();
const suffix = rawDesc ? `, ${rawDesc}` : '';
let display = `[[${p.title}]]`;
if (includeIll) {
const ill = this.getPreferredSitelink(p.ent);
if (ill) display = `{{ill|${p.title}|${ill.lang}|${ill.title}}}`;
}
return `* ${display}${bio}${suffix}`;
};
GetterDialog.prototype.getActionProcess = function(action) {
if (action === 'generate') return new OO.ui.Process(async () => { await this.runGetter(); });
if (action === 'back') {
if (this.abortController) this.abortController.abort();
return new OO.ui.Process(() => {
this.resultContainer.$element.hide();
this.fieldset.$element.show();
this.actions.setMode('edit');
});
}
if (action === 'exit') { if (this.abortController) this.abortController.abort(); return new OO.ui.Process(() => this.close()); }
return GetterDialog.super.prototype.getActionProcess.call(this, action);
};
GetterDialog.prototype.getBestAnchor = function() {
const getValues = (w) => w.getValue().map(t => t.trim()).filter(Boolean);
const candidates = [...getValues(this.surnameTags).map(t => ({ term: t, field: 'surname', weight: 2000 + this.score(t) })), ...getValues(this.givenTags).map(t => ({ term: t, field: 'given', weight: 1000 + this.score(t) })), ...getValues(this.middleTags).map(t => ({ term: t, field: 'middle', weight: 0 + this.score(t) }))];
return candidates.length === 0 ? null : candidates.reduce((b, c) => (c.weight > b.weight ? c : b));
};
GetterDialog.prototype.getCompiledCriteria = function() { return { given: this.compileCriteria(this.givenTags.getValue()), surname: this.compileCriteria(this.surnameTags.getValue()), middle: this.compileCriteria(this.middleTags.getValue()) }; };
GetterDialog.prototype.getExistingLinks = async function() {
const titles = new Set();
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const regexes = [/\[\[([^\]|#]+)/g, /\{\{(?:anbl|Annotated biography link)\|([^}|]+)/gi];
regexes.forEach(re => { let m; while ((m = re.exec(text)) !== null) titles.add(this.normalizeTitle(m[1])); });
} catch (e) { console.error('[Getter] Parse error:', e); }
return titles;
};
GetterDialog.prototype.getPageText = function() { return this.pageTextPromise ??= this.api.get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'wikitext', format: 'json' }); };
GetterDialog.prototype.getPreferredSitelink = function(ent) {
const sitelinks = ent.sitelinks || {};
const key = LANG_PREFS.find(l => sitelinks[l]) || Object.keys(sitelinks).find(k => k.endsWith('wiki') && k !== 'enwiki' && !NON_LANG_WIKIS.includes(k));
return key ? { lang: key.replace(/wiki$/, ''), title: sitelinks[key].title } : null;
};
GetterDialog.prototype.getSetupProcess = function(data) {
return GetterDialog.super.prototype.getSetupProcess.call(this, data).next(() => {
if (data && data.pageTitle) {
if (this.givenTags.getValue().indexOf(data.pageTitle) === -1) {
this.givenTags.addTag(data.pageTitle);
}
}
this.autoPopulateFields();
});
};
GetterDialog.prototype.getSmartHeading = function(startItem, endItem) {
const char1 = startItem.sortKey.charAt(0).toUpperCase();
const char2 = endItem.sortKey.charAt(0).toUpperCase();
return char1 === char2 ? char1 : `${char1}–${char2}`;
};
GetterDialog.prototype.getSortKey = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return parts.length <= 1 ? clean.toLowerCase() : (parts[parts.length - 1] + ', ' + parts.slice(0, -1).join(' ') + ' | ' + clean).toLowerCase();
};
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
GetterDialog.prototype.matchesCriteria = function(title, compiled) {
const parts = this.tokenizeTitle(title);
const check = (val, regs) => regs.length === 0 || regs.some(re => re.test(val));
let givenMatch = check(parts.given, compiled.given);
if (!givenMatch && compiled.given.length > 0) {
const allTokens = this.cleanTitle(title).toLowerCase().split(/\s+/);
givenMatch = compiled.given.some(re => allTokens.some(token => re.test(token)));
}
const surnameMatch = check(parts.surname, compiled.surname);
const middleMatch = compiled.middle.length === 0 || compiled.middle.some(re => parts.middle.some(t => re.test(t)));
if (title.startsWith('Aaron')) {
console.log({
title,
parts,
givenMatch,
surnameMatch,
middleMatch,
compiled
});
}
return givenMatch && surnameMatch && middleMatch;
};
GetterDialog.prototype.normalizeTitle = (t) => { const title = mw.Title.newFromText(t); return title ? title.getPrefixedText() : t.replace(/_/g, ' ').trim(); };
GetterDialog.prototype.processEntityBatch = async function(pageIds, signal) {
const confirmed = new Map();
const pD = await this.api.get({ action: 'query', prop: 'pageprops|description', pageids: pageIds.join('|'), format: 'json' }, { signal });
const qidMap = {},
qidList = [];
Object.values(pD.query.pages).forEach(p => {
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p;
}
});
if (!qidList.length) return confirmed;
const entities = await this.fetchEntities(qidList, signal);
for (const qid in entities) {
const ent = entities[qid],
p = qidMap[qid],
isH = ent.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
if ((this.humanOnly.isSelected() && !isH) || (this.excludeHumans.isSelected() && isH)) continue;
confirmed.set(p.title, { title: p.title, desc: p.description || ent.descriptions?.en?.value || '', ent });
}
return confirmed;
};
GetterDialog.prototype.refreshPreviews = function() {
this.previewCache = {};
this.previewPending.clear();
this.previewGeneration++;
this.refreshUI();
};
GetterDialog.prototype.refreshUI = function() {
this.col1.empty();
this.col2.empty();
this.col3.empty();
this.col4.empty();
this.previewElements.clear();
const $ul = $('<ul>').css({ 'list-style-type': 'disc', 'padding-left': '20px', margin: '0' });
this.col4.append($ul);
const fSize = window.GetterConfig.fontSize;
const itemStyle = {
cursor: 'pointer',
padding: '2px',
'font-size': fSize,
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
};
this.items.forEach(item => {
const $w = $('<div>').text(item.wikitext).attr('title', item.wikitext).css({ ...itemStyle, opacity: item.status === 'wikitext' ? 0.3 : 1 });
$w.on('click', () => {
item.status = (item.status === 'wikitext' ? null : 'wikitext');
this.refreshUI();
});
this.col1.append($w);
const $a = $('<div>').text(item.anbl).attr('title', item.anbl).css({ ...itemStyle, opacity: item.status === 'anbl' ? 0.3 : 1 });
$a.on('click', () => {
item.status = (item.status === 'anbl' ? null : 'anbl');
this.refreshUI();
});
this.col3.append($a);
const $li = $('<li>').attr('title', item.page.title).css({
'font-size': fSize,
'margin-bottom': '2px',
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
}).html(this.previewCache[item.page.title] || '<em>Loading...</em>');
$ul.append($li);
this.previewElements.set(item.page.title, $li[0]);
});
this.items.filter(i => i.status).forEach(item => {
const text = item.status === 'wikitext' ? item.wikitext : item.anbl;
this.col2.append($('<div>').text(text).attr('title', text).css({ 'margin-bottom': '4px', 'font-size': fSize }));
});
const missing = this.items.filter(i => !(i.page.title in this.previewCache) && !this.previewPending.has(i.page.title));
if (missing.length) this.batchFetchPreviews(missing);
};
GetterDialog.prototype.renderResults = async function(confirmed) {
this.previewGeneration++;
this.previewPending.clear();
this.items = Array.from(confirmed.values()).map(p => ({
page: p,
sortKey: this.sortBySurname.isSelected() ? this.getSortKey(p.title) : this.cleanTitle(p.title).toLowerCase(),
wikitext: this.formatResult(p, 'wiki', this.includeIll.isSelected()),
anbl: this.formatResult(p, 'anbl', this.includeIll.isSelected()),
status: null
})).sort((a, b) => a.sortKey.localeCompare(b.sortKey, undefined, { sensitivity: 'base' }));
this.refreshUI();
this.fieldset.$element.hide();
this.resultContainer.$element.show();
this.resultControls.$element.show();
this.actions.setMode('result');
};
GetterDialog.prototype.runGetter = async function() {
this.abortController = new AbortController();
this.cachedResults = null;
try {
const best = this.getBestAnchor();
if (!best) { mw.notify('Please enter search terms.', { type: 'error' }); return; }
const existing = this.updateMode.isSelected() ? await this.getExistingLinks() : new Set();
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), this.excludeTags.getValue().map(e => e.trim().toLowerCase()), existing, this.abortController.signal);
const confirmed = new Map(),
pageIds = Array.from(resultsMap.values()).map(p => p.pageid);
for (let i = 0; i < pageIds.length; i += 50) {
const batchConfirmed = await this.processEntityBatch(pageIds.slice(i, i + 50), this.abortController.signal);
batchConfirmed.forEach((val, key) => confirmed.set(key, val));
}
this.cachedResults = confirmed;
this.renderResults(confirmed);
} catch (e) { if (e.name !== 'AbortError') mw.notify('Error generating list.', { type: 'error' }); } finally { this.abortController = null; }
};
GetterDialog.prototype.score = (term) => { const w = (term.match(/[*?]/g) || []).length; return (w === 0 ? 1000 : 0) + term.replace(/[*?]/g, '').length - w; };
GetterDialog.prototype.setPreviewError = function(items) {
items.forEach(item => {
this.previewCache[item.page.title] ??= PREVIEW_ERROR_HTML;
const targetEl = this.previewElements.get(item.page.title);
if (targetEl) targetEl.innerHTML = this.previewCache[item.page.title];
});
};
GetterDialog.prototype.setupUI = function() {
this.givenTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Given names...', allowArbitrary: true });
this.middleTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Middle terms...', allowArbitrary: true });
this.surnameTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Surnames...', allowArbitrary: true });
this.excludeTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Exclude strings...', allowArbitrary: true });
this.humanOnly = new OO.ui.CheckboxInputWidget({ selected: true });
this.excludeHumans = new OO.ui.CheckboxInputWidget({ selected: false });
this.includeIll = new OO.ui.CheckboxInputWidget({ selected: false });
this.sortBySurname = new OO.ui.CheckboxInputWidget({ selected: true });
this.updateMode = new OO.ui.CheckboxInputWidget({ selected: false });
this.mode = new OO.ui.RadioSelectInputWidget({
options: [{ data: 'none', label: 'No headings' }, { data: 'smart', label: 'Smart grouping' }, { data: 'alpha', label: 'A-Z' }],
value: 'none'
});
this.level = new OO.ui.RadioSelectInputWidget({
options: [{ data: 2, label: 'Level 2' }, { data: 3, label: 'Level 3' }, { data: 4, label: 'Level 4' }],
value: 3
});
if (!this.level || !this.mode) {
console.error('Level or mode widget missing during UI setup');
}
mw.util.addCSS(` .getter-result-fieldset .oo-ui-fieldsetLayout-header {
font-size: 13px !important;
color: #444;
}
.getter-formatting-container {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 40px;
}
.getter-formatting-container .oo-ui-fieldLayout {
display: flex;
align-items: center;
margin-top: 2px;
}
.getter-formatting-container .oo-ui-fieldLayout-label {
margin-right: 12px;
font-weight: bold;
width: auto;
flex-shrink: 0;
line-height: 1.4;
}
.oo-ui-radioSelectInputWidget {
vertical-align: middle;
}
`);
this.resultControls = new OO.ui.FieldsetLayout({ label: 'Output Formatting', classes: ['getter-result-fieldset'] });
const $formattingRow = $('<div>').addClass('getter-formatting-container');
const modeLayout = new OO.ui.FieldLayout(this.mode, {
label: 'Grouping Mode',
align: 'top'
});
const levelLayout = new OO.ui.FieldLayout(this.level, {
label: 'Heading Level',
align: 'top'
});
$formattingRow.append(modeLayout.$element, levelLayout.$element);
this.resultControls.$element.append($formattingRow);
this.fieldset = new OO.ui.FieldsetLayout({ label: 'Advanced Filter Settings' });
this.fieldset.addItems([
new OO.ui.FieldLayout(this.givenTags, { label: 'Given names', align: 'top' }),
new OO.ui.FieldLayout(this.middleTags, { label: 'Middle terms', align: 'top' }),
new OO.ui.FieldLayout(this.surnameTags, { label: 'Surnames', align: 'top' }),
new OO.ui.FieldLayout(this.excludeTags, { label: 'Exclude strings', align: 'top' }),
new OO.ui.FieldLayout(this.humanOnly, { label: 'Humans only', align: 'inline' }),
new OO.ui.FieldLayout(this.excludeHumans, { label: 'Exclude humans', align: 'inline' }),
new OO.ui.FieldLayout(this.sortBySurname, { label: 'Sort by surname', align: 'inline' }),
new OO.ui.FieldLayout(this.updateMode, { label: 'Exclude existing links', align: 'inline' }),
new OO.ui.FieldLayout(this.includeIll, { label: 'Include {{ill}}', align: 'inline' })
]);
const colStyle = { height: '480px', 'overflow-y': 'auto', 'overflow-x': 'hidden', border: '1px solid #ccc', padding: '10px', 'white-space': 'nowrap', 'text-overflow': 'ellipsis' };
this.col1 = $('<div>').css(colStyle);
this.col2 = $('<div>').css({ ...colStyle, background: '#f9f9f9' });
this.col3 = $('<div>').css(colStyle);
this.col4 = $('<div>').css(colStyle);
const columns = [this.col1, this.col2, this.col3, this.col4];
columns.forEach(col => {
col.on('scroll', (e) => {
const scrollTop = e.target.scrollTop;
columns.forEach(otherCol => { if (otherCol[0] !== e.target && otherCol[0].scrollTop !== scrollTop) otherCol[0].scrollTop = scrollTop; });
});
});
const $refreshBtn = new OO.ui.ButtonWidget({ label: 'Refresh', icon: 'reload', flags: ['progressive'] });
$refreshBtn.on('click', () => this.refreshPreviews());
this.resultContainer = new OO.ui.PanelLayout({ padded: true, expanded: true });
const $columns = $('<div>').css({ display: 'flex', gap: '10px' });
const createHeader = (label) => new OO.ui.LabelWidget({ label: label }).$element.css({ 'font-weight': 'bold', 'font-size': '13px', 'margin-bottom': '5px' });
$columns.append($('<div>').css('flex', '1.2').append(createHeader('Wikitext'), this.col1, this.createButtonRow('wikitext')), $('<div>').css('flex', '1.2').append(createHeader('Chosen List'), this.col2, this.createButtonRow('list')), $('<div>').css('flex', '0.8').append(createHeader('Anbl'), this.col3, this.createButtonRow('anbl')), $('<div>').css('flex', '1.2').append(createHeader('Anbl Preview'), this.col4, $('<div>').css('margin-top', '10px').append($refreshBtn.$element)));
this.resultContainer.$element.append($columns, $('<div>').css('margin-top', '20px').append(this.resultControls.$element)).hide();
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.panel.$element.append(this.fieldset.$element, this.resultContainer.$element);
};
GetterDialog.prototype.silentScan = async function() {
if (window.GetterConfig.enableSilentScan === false || !/(?:list of|surname|given name)/i.test(mw.config.get('wgTitle'))) return;
$('.mw-notification[data-notification-tag="getter-discovery"]').remove();
await this.ensureFieldsInitialized();
const best = this.getBestAnchor();
if (!best) return;
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), [], await this.getExistingLinks(), null);
if (!resultsMap.size) return;
const confirmed = await this.processEntityBatch(Array.from(resultsMap.values()).map(p => p.pageid).slice(0, 50), null);
if (confirmed.size > 0) {
this.cachedResults = confirmed;
const $link = $('<a>', { text: 'Review', href: '#', style: 'font-weight:bold; cursor:pointer' });
$link.on('click', (e) => {
e.preventDefault();
$link.closest('.mw-notification').remove();
this.windowManager.openWindow(this);
this.renderResults(this.cachedResults);
});
mw.notify($('<span>').text(`Getter found ${resultsMap.size > 50 ? '50+' : confirmed.size} potential entries. `).append($link), { tag: 'getter-discovery', autoHide: false });
}
};
GetterDialog.prototype.tokenizeTitle = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return {
given: parts[0] || '',
surname: parts.length > 1 ? parts[parts.length - 1] : '',
middle: parts.length > 2 ? parts.slice(1, -1) : []
};
};
// --- Initialization ---
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui', 'oojs-ui-widgets', 'oojs-ui-windows']).then(() => {
const wm = new OO.ui.WindowManager();
$(document.body).append(wm.$element);
const dialog = new GetterDialog({ size: 'full' });
dialog.windowManager = wm;
wm.addWindows([dialog]);
$(mw.util.addPortletLink('p-tb', '#', 'Getter', 't-getter')).on('click', (e) => {
e.preventDefault();
wm.openWindow(dialog);
});
});
}(mediaWiki, jQuery, OO));
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "0.5",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 10,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
/* global ComradeLib, mw, $ */
(async function() {
try {
await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-options.js&action=raw&ctype=text/javascript');
} catch (e) {
console.warn("Comrade: Could not load /comrade-options.js, using fallback defaults.", e);
}
window.ComradeOptions = Object.assign({
version: "0.5.0",
excludedTemplates: [],
nudgeTimeout: 10000,
debugMode: false
}, window.ComradeOptions || {});
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 res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qid}&props=claims|descriptions|labels&languages=en&format=json&origin=*`).then(r => r.json());
return res.entities[qid];
}
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;
ComradeLib.checkLeadCapitalization(currentTitle);
// 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);
await ComradeLib.checkNoShortDesc(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 isRedirect = !!mw.config.get("wgIsRedirect");
const userExclusions = window.comradeQualityExclusions || [];
const allExclusions = [...window.ComradeOptions.excludedTemplates, ...userExclusions];
const exclusionRegexString = allExclusions.map(t => t.replace("Template:", "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
const exclusionRegex = new RegExp(exclusionRegexString, 'i');
if (isRedirect || exclusionRegex.test(wikitext)) {
console.log("Comrade check skipped: Page is a redirect or excluded via options.");
return;
}
ComradeLib.checkQualityConsistency();
} catch (err) {
console.error("Comrade error:", err);
}
}
init();
})();
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Search') {
return;
}
mw.loader.using(['mediawiki.util']).then(function() {
$(function() {
const $resultsInfo = $('.results-info');
const totalResultsRaw = $resultsInfo.attr('data-mw-num-results-total') || '0';
function formatWithCommas(numberString) {
return numberString.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
const state = {
language: mw.config.get('wgContentLanguage'),
totalResultsFormatted: formatWithCommas(totalResultsRaw)
};
let titlesCache = null;
function extractResultTitles() {
const currentResults = $('.mw-search-result-heading');
if (!titlesCache || titlesCache.length !== currentResults.length) {
titlesCache = currentResults.map(function() {
return $(this).find('a:first').attr('title');
}).get().filter(Boolean);
}
return titlesCache;
}
mw.hook("wikipage.content").add(function() {
titlesCache = null;
});
function addLimitOptions() {
const selects = document.querySelectorAll('.cdx-table-pager__limit-form select[name="limit"]');
if (!selects.length) return;
const valuesToAdd = ["1000", "2500", "5000"];
selects.forEach(function(select) {
valuesToAdd.forEach(function(value) {
if (select.querySelector('option[value="' + value + '"]')) return;
const option = document.createElement("option");
option.value = value;
option.textContent = formatWithCommas(value) + " items";
const existingOptions = Array.from(select.querySelectorAll("option"));
const insertBefore = existingOptions.find(function(opt) {
return Number(opt.value) > Number(value);
});
if (insertBefore) {
select.insertBefore(option, insertBefore);
} else {
select.appendChild(option);
}
});
});
}
mw.hook("wikipage.content").add(addLimitOptions);
const numLinks = $('.mw-numlink');
if (numLinks.length > 4 && !numLinks.eq(4).next().is('a[href*="limit=5000"]')) {
const $firstAnchor = numLinks.eq(4);
const $firstSpan = $('<span> | </span>').insertAfter($firstAnchor);
const url1 = new URL(window.location.href);
url1.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url1.toString()
}).insertAfter($firstAnchor).before($firstSpan);
}
if (numLinks.length > 9 && !numLinks.eq(9).next().is('a[href*="limit=5000"]')) {
const $secondAnchor = numLinks.eq(9);
const $secondSpan = $('<span> | </span>').insertAfter($secondAnchor);
const url2 = new URL(window.location.href);
url2.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url2.toString()
}).insertAfter($secondAnchor).before($secondSpan);
}
const $container = $("<div></div>").addClass("enhanced-search-container").hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const interwikiResults = document.getElementById("mw-interwiki-results");
if (interwikiResults) {
$container.addClass("enhanced-search-floating").insertBefore("#mw-interwiki-results");
} else {
$container.addClass("enhanced-search-absolute").insertBefore(".searchresults");
}
const totalFormattedStr = state.totalResultsFormatted;
const $titleElement = $("<div>Show a shortened list of<br />this page's " + $('.mw-search-result-heading').length + " results <small>(of total of " + totalFormattedStr + ")</small></div>").addClass("enhanced-header").appendTo($container);
const $contentArea = $("<div></div>").addClass("enhanced-content-area").appendTo($container).hide();
const $note1 = $("<div>Copy this page\'s " + $('.mw-search-result-heading').length + " results to the clipboard as...</div>").addClass("enhanced-note").insertAfter($container).hide();
function setupHoverTooltip(element, message) {
$(element).attr('title', message).hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
}
const $note2 = $("<div>...plain text<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note1).hide();
setupHoverTooltip($note2, 'This button copies this page\'s results to the clipboard as plain text, i.e. without wikitext markup. Since this copies only the results shown on this search page, make sure you have clicked the "view 20, 50, 100, 250, 500, 5000" link. This format is compatible with PetScan and AutoWikiBrowser (AWB) for example.');
const $note3 = $("<div>...wikitext<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note2).hide();
setupHoverTooltip($note3, 'This button copies this page\'s results to the clipboard and also adds wikitext markup.');
const $note4 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note3).hide();
setupHoverTooltip($note4, 'This button copies a PetScan URL with this page\'s results to the clipboard. Note that if the PetScan URL becomes too long (~250+ pages), the URL might not work.');
const $note5 = $("<div>...or this search query's total results of " + totalFormattedStr + "<sup> (?)</sup> as...</div>").addClass("enhanced-note").insertAfter($note4).hide().attr('title', 'Note that the actual limit is 10,000, which is MediaWiki\'s maximum limit.').hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const $note6 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note5).hide();
setupHoverTooltip($note6, 'This button copies a PetScan URL with all ' + totalFormattedStr + ' results to the clipboard. Note that the maximum limit is 10,000 due to MediaWiki\'s restriction.');
const $note7 = $("<div><sup>(?)</sup> = hover over for tooltip details</div>").addClass("enhanced-note-right").insertAfter($note6).hide();
const noteElements = [$note1, $note2, $note3, $note4, $note5, $note6, $note7];
if (interwikiResults !== null) {
noteElements.forEach($el => $el.addClass("enhanced-floating-elements"));
} else {
noteElements.forEach(function($el, index) {
$el.addClass("enhanced-absolute-elements enhanced-position-" + index);
});
}
function formatPlain() {
return extractResultTitles().join("\r\n");
}
function formatWikitext() {
const titles = extractResultTitles().map(title => "* [[" + title + "]]");
const header = "This is a list of [" + window.location.href + " these search results] (made with the script [[User:JAAqqO/EnhancedSearchPage|EnhancedSearchPage]]):\r\n";
return header + titles.join("\r\n");
}
function formatPetScan() {
const encodedTitles = extractResultTitles().map(encodeURIComponent).join("%0A");
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&manual_list=" + encodedTitles + "&manual_list_wiki=" + state.language + "wiki&project=wikipedia&language=" + state.language + "&doit=";
}
function formatPetScanTotal() {
const oouiInput = document.getElementById("ooui-php-1");
const value = oouiInput ? oouiInput.value : "";
if (value) {
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&ns%5B0%5D=1&search_query=" + encodeURIComponent(value) + "&search_wiki=" + state.language + "wiki&search_max_results=10000&project=wikipedia&language=" + state.language + "&doit=";
}
return "";
}
function copyToClipboard(text) {
if (!text) return;
if (!navigator.clipboard) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
} catch (e) {
console.error('Fallback copy failed', e);
}
textarea.remove();
} else {
navigator.clipboard.writeText(text).catch(function(err) {
console.error("Copy failed: ", err);
});
}
}
function showCopiedMessage(event) {
const posX = event.pageX;
const posY = event.pageY;
const $msg = $("<div class='copied-message'>Copied!</div>").appendTo("body");
$msg.css({
position: "absolute",
top: (posY + 10) + "px",
left: (posX + 10) + "px",
backgroundColor: "#f0f0f0",
border: "1px solid grey",
padding: "5px 10px",
borderRadius: "5px",
zIndex: 9999
});
setTimeout(function() {
$msg.fadeOut("slow", function() {
$(this).remove();
});
}, 500);
}
function performButtonAnimation($element) {
$element.css("transform", "scaleY(0.9)").animate({
opacity: '0.5'
}, "fast", function() {
$(this).animate({
opacity: '1'
}, "slow").css("transform", "scaleY(1)");
});
}
let isExpanded = false;
function showContentArea() {
if (isExpanded) return;
isExpanded = true;
const currentResults = $('.mw-search-result-heading');
$contentArea.empty().append(currentResults.clone());
$contentArea.slideDown();
$(".searchalttitle").remove();
$container.addClass("enhanced-active-container");
$titleElement.remove();
noteElements.forEach($el => $el.show());
}
function bindCopyAction($element, formatterFn) {
$element.click(function(event) {
copyToClipboard(formatterFn());
showCopiedMessage(event);
performButtonAnimation($(this));
});
}
$container.click(showContentArea);
bindCopyAction($note2, formatPlain);
bindCopyAction($note3, formatWikitext);
bindCopyAction($note4, formatPetScan);
bindCopyAction($note6, formatPetScanTotal);
const $pageDown = $("<div><br /><br />[ ↓ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the bottom of the page');
}, function() {
$(this).removeClass("hover");
}).insertBefore('.mw-search-result:eq(0)');
const $pageUp = $("<div>[ ↑ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the top of the page');
}, function() {
$(this).removeClass("hover");
}).insertAfter('.mw-search-result:eq(-1)');
$pageDown.click(function(event) {
event.preventDefault();
$('html, body').animate({
scrollTop: $(document).height()
}, 500);
});
$pageUp.click(function() {
$('html, body').animate({
scrollTop: 0
}, 500);
});
$("<span> (<b>" + $('.mw-search-result-heading').length + "</b> shown on the page)</span>").appendTo('.results-info');
const searchDidYouMean = document.querySelector('.searchdidyoumean');
if (searchDidYouMean) {
searchDidYouMean.style.color = '#b32424';
searchDidYouMean.style.fontSize = '20px';
}
});
});
})();
//</nowiki>
t9r8enu1r2moe2exkckwejxade7rbcf
769939
769932
2026-10-24T10:38:02Z
Sam Sailor
26820
Test
769939
javascript
text/javascript
//<nowiki>
(function(mw, $) {
'use strict';
if (mw.config.get('wgNamespaceNumber') !== 0) return;
const UnisexGenderAppender = {
init() {
// Check if page belongs to Category:Unisex given names or contains the template
const categories = mw.config.get('wgCategories') || [];
const isUnisexPage = categories.some(c => c.includes('Unisex given names')) || $('.mw-parser-output').text().includes('unisex given name');
if (!isUnisexPage) return;
// Add portlet button to p-cactions
const link = mw.util.addPortletLink('p-cactions', '#', 'Append gender', 'ca-unisex-gender', 'Fetch and append gender symbols from Wikidata');
if (link) {
$(link).on('click', (e) => {
e.preventDefault();
$(link).off('click').find('a').text('Appending genders...');
this.run();
});
}
},
// Collect all article titles linked in lists/bullet points on the page
async run() {
const titleMap = new Map(); // title -> $element
const titles = [];
let appendedCount = 0;
$('#mw-content-text li a[title]').each(function() {
const $a = $(this);
const title = $a.attr('title');
// Skip red links, help pages, or main namespaces other than 0
if ($a.hasClass('new') || title.includes(':')) return;
if (!titleMap.has(title)) {
titleMap.set(title, $a);
titles.push(title);
}
});
if (titles.length === 0) {
mw.notify('No valid article links found to process.', {
type: 'warn'
});
return;
}
const api = new mw.Api();
// Batch query titles in chunks of 50 to get their wikibase item (QID)
for (let i = 0; i < titles.length; i += 50) {
const chunk = titles.slice(i, i + 50);
try {
const data = await api.get({
action: 'query',
titles: chunk.join('|'),
prop: 'pageprops',
redirects: true,
format: 'json'
});
const query = data.query || {};
const pages = query.pages || {};
const redirectMap = {};
// Build a map if any redirects occurred
if (query.redirects) {
query.redirects.forEach(r => {
redirectMap[r.from] = r.to;
});
}
const qidMap = {};
const qidList = [];
Object.values(pages).forEach(p => {
// Ignore missing pages (negative page IDs)
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p.title;
}
});
if (!qidList.length) continue;
// Fetch P31 and P21 from Wikidata API
const wdRes = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qidList.join('|')}&props=claims&format=json&origin=*`);
const wdData = await wdRes.json();
const entities = wdData.entities || {};
for (const qid in entities) {
const claims = entities[qid].claims || {};
// Check Property P31 (instance of): must be Q5 (human)
const isHuman = claims.P31?.some(c => c.mainsnak?.datavalue?.value?.id === 'Q5');
if (!isHuman) continue;
const canonicalTitle = qidMap[qid];
// Find all original DOM elements that targeted either the canonical title OR redirected to it
const matchingOriginalTitles = [canonicalTitle];
for (const [fromTitle, toTitle] of Object.entries(redirectMap)) {
if (toTitle === canonicalTitle) {
matchingOriginalTitles.push(fromTitle);
}
}
// Append symbol right after the link
matchingOriginalTitles.forEach(origTitle => {
const $a = titleMap.get(origTitle);
if (!$a) return;
const p21 = claims.P21?.[0]?.mainsnak?.datavalue?.value?.id;
let symbol = ' ?';
let titleAttr = 'Gender: Unknown or ambiguous';
if (p21 === 'Q6581097' || p21 === 'Q2449503') {
symbol = ' ♂';
titleAttr = p21 === 'Q2449503' ? 'Gender: Transgender male' : 'Gender: Male';
} else if (p21 === 'Q6581072' || p21 === 'Q1052281') {
symbol = ' ♀';
titleAttr = p21 === 'Q1052281' ? 'Gender: Transgender female' : 'Gender: Female';
} else if (p21 === 'Q1097630') {
symbol = ' ☿';
titleAttr = 'Gender: Intersex';
}
// Avoid appending twice if multiple variants match
if ($a.next('.gender-symbol').length === 0) {
$('<span class="gender-symbol" style="font-size: 0.9em; margin-left: 4px; cursor: default; user-select: none; -webkit-user-select: none; -moz-user-select: none;" title="' + titleAttr + '"></span>').text(symbol).insertAfter($a);
appendedCount++;
}
});
}
} catch (e) {
console.error('[UnisexGenderAppender] Error fetching batch:', e);
}
}
if (appendedCount > 0) {
mw.notify(`Appended gender symbols to ${appendedCount} entries!`, {
type: 'success'
});
} else {
mw.notify('Scan complete, but no matching entries were updated.', {
type: 'warn'
});
}
$('#ca-unisex-gender a').text('Genders appended');
}
};
$(() => UnisexGenderAppender.init());
})(mediaWiki, jQuery);
(function(mw, $, OO) {
'use strict';
window.getterWindowManager = null;
window.GetterConfig = Object.assign({
enableSilentScan: true,
fontSize: '12px'
}, window.GetterConfig || {});
const IGNORE_SUFFIXES = ['Sr.', 'Sr', 'Jr.', 'Jr', 'II', 'III', 'IV', 'V', 'MD', 'PhD'];
const LANG_PREFS = ['frwiki', 'dewiki', 'eswiki', 'itwiki', 'nlwiki', 'svwiki'];
const NON_LANG_WIKIS = ['commonswiki', 'specieswiki', 'metawiki', 'mediawikiwiki', 'wikidatawiki', 'wikimaniawiki'];
const suffixRE = new RegExp('\\s(' + IGNORE_SUFFIXES.join('|').replace(/\./g, '\\.') + ')$', 'i');
const PREVIEW_ERROR_HTML = '<em style="color:#b32424">Preview unavailable</em>';
// --- Constructors ---
function GetterDialog(config) {
GetterDialog.super.call(this, config);
this.abortController = null;
this.api = new mw.Api();
this.fieldsInitPromise = null;
this.pageTextPromise = null;
this.cachedResults = null;
this.items = [];
this.previewCache = {};
this.previewElements = new Map();
this.previewPending = new Set();
this.previewGeneration = 0;
this.setupUI();
}
OO.inheritClass(GetterDialog, OO.ui.ProcessDialog);
GetterDialog.static.name = 'getterDialog';
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
// --- Static Definitions ---
GetterDialog.static.name = 'getterDialog';
GetterDialog.static.title = 'Getter: Wikipedia List Builder';
GetterDialog.static.actions = [{ action: 'generate', label: 'Generate List', flags: ['primary', 'progressive'], modes: 'edit' }, { action: 'back', label: 'Go back', modes: 'result' }, { action: 'exit', label: 'Exit', flags: 'safe', modes: ['edit', 'result'] }];
// --- GetterDialog Prototype Methods (Alphabetical) ---
GetterDialog.prototype.autoPopulateFields = async function() {
const title = mw.config.get('wgTitle'),
clean = this.cleanTitle(title);
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const given = text.match(/\{\{\s*given name\b[^}]*\}\}/i),
surname = /\{\{\s*surname\s*(?:\||\}\})/i.test(text);
if (surname || (given && /\|\s*type\s*=\s*both\b/i.test(given[0]))) this.surnameTags.addTag(clean);
if (given) this.givenTags.addTag(clean);
if (!surname && !given) this.givenTags.addTag(clean);
} catch (e) { this.givenTags.addTag(clean); }
};
GetterDialog.prototype.batchFetchPreviews = async function(items) {
const generation = this.previewGeneration;
for (let i = 0; i < items.length; i += 50) {
const chunk = items.slice(i, i + 50);
chunk.forEach(item => this.previewPending.add(item.page.title));
try {
const text = chunk.map(item => `<div data-getter-preview="${mw.html.escape(item.page.title)}">{{anbl|${item.page.title}}}</div>`).join('\n');
const data = await this.api.post({ action: 'parse', text: text, contentmodel: 'wikitext', prop: 'text' });
if (generation !== this.previewGeneration) return;
const $results = $(data.parse.text['*']).find('[data-getter-preview]');
$results.each((_, el) => {
const title = el.dataset.getterPreview;
$(el).find('a').attr('target', '_blank').attr('rel', 'noopener');
const html = el.innerHTML;
if (title && this.previewPending.has(title)) {
this.previewCache[title] = html;
const targetEl = this.previewElements.get(title);
if (targetEl) targetEl.innerHTML = html;
}
});
} catch (e) {
console.error('[Getter] Batch parse error:', e);
if (generation === this.previewGeneration) this.setPreviewError(chunk);
} finally {
chunk.forEach(item => this.previewPending.delete(item.page.title));
}
}
};
GetterDialog.prototype.cleanTitle = (t) => t.replace(suffixRE, '').replace(/\s?\([^)]*\)$/, '').trim();
GetterDialog.prototype.compileCriteria = (vals) => vals.map(v => v.trim()).filter(Boolean).map(v => new RegExp('^' + v.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\?/g, '.').replace(/\*/g, '.*') + '$', 'i'));
GetterDialog.prototype.copyToClipboard = async function(text) {
if (window.isSecureContext && navigator.clipboard?.writeText) {
try { await navigator.clipboard.writeText(text); return true; } catch { /* empty */ }
}
const $ta = $('<textarea>').val(text).css({ position: 'fixed', top: '-1000px', left: '-1000px' }).appendTo(document.body);
const el = $ta[0];
el.focus();
el.select();
const success = document.execCommand('copy');
$ta.remove();
return success;
};
GetterDialog.prototype.createButtonRow = function(colType) {
const $row = $('<div>').css({ display: 'flex', 'flex-wrap': 'wrap', gap: '5px', 'margin-top': '10px' });
const performCopy = async (shouldExit) => {
console.log('performCopy triggered for:', colType);
if (!this.level || !this.mode) {
mw.notify('Formatting widgets not ready yet.', { type: 'error' });
return;
}
const selectedItems = (colType === 'list') ? this.items.filter(i => i.status !== null) : this.items;
if (selectedItems.length === 0) {
mw.notify('No items to copy.', { type: 'warn' });
return;
}
try {
const level = this.level.getValue();
const mode = this.mode.getValue();
const getText = (i) => {
if (colType === 'list') return i.status === 'wikitext' ? i.wikitext : i.anbl;
if (colType === 'wikitext') return i.wikitext;
return i.anbl;
};
const formatted = this.formatListWithHeadings(selectedItems, level, mode, getText);
const success = await this.copyToClipboard(formatted);
if (success) {
mw.notify(`Copied ${selectedItems.length} items!`, { type: 'success' });
if (shouldExit) this.close();
}
} catch (e) {
console.error('Copy error:', e);
mw.notify('Failed to copy list.', { type: 'error' });
}
};
const copyBtn = new OO.ui.ButtonWidget({ label: 'Copy' });
copyBtn.on('click', () => performCopy(false));
$row.append(copyBtn.$element);
const exitBtn = new OO.ui.ButtonWidget({ label: 'Copy & Exit', flags: ['progressive'] });
exitBtn.on('click', () => performCopy(true));
$row.append(exitBtn.$element);
if (colType === 'wikitext' || colType === 'anbl') {
const toggleBtn = new OO.ui.ButtonWidget({ label: 'Toggle All' });
toggleBtn.on('click', () => {
const allSet = this.items.every(i => i.status === colType);
this.items.forEach(i => i.status = allSet ? null : colType);
this.refreshUI();
});
const smartBtn = new OO.ui.ButtonWidget({ label: 'Smart Add' });
smartBtn.on('click', () => {
this.items.forEach(i => { if (!i.status) i.status = colType; });
this.refreshUI();
});
$row.append(toggleBtn.$element, smartBtn.$element);
} else if (colType === 'list') {
const clearBtn = new OO.ui.ButtonWidget({ label: 'Clear', flags: ['destructive'] });
clearBtn.on('click', () => {
this.items.forEach(i => i.status = null);
this.refreshUI();
});
$row.append(clearBtn.$element);
}
return $row;
};
GetterDialog.prototype.ensureFieldsInitialized = function() {
if (!this.fieldsInitPromise) {
this.fieldsInitPromise = (async () => {
this.givenTags.clearItems();
this.middleTags.clearItems();
this.surnameTags.clearItems();
await this.autoPopulateFields();
})();
}
return this.fieldsInitPromise;
};
GetterDialog.prototype.fetchEntities = async function(qids, signal) {
const all = {};
for (let i = 0; i < qids.length; i += 50) {
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');
const res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qids.slice(i, i + 50).join('|')}&props=claims|labels|descriptions|sitelinks&languages=en&format=json&origin=*`, { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
Object.assign(all, data.entities || {});
}
return all;
};
GetterDialog.prototype.findCandidates = async function(bestAnchor, compiled, excludeList, existing, signal) {
const resultsMap = new Map();
const term = bestAnchor.term.replace(/"/g, '\\"').trim();
const isWildcard = /[*?]/.test(term);
const DEBUG = true;
let searches = [];
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
searches.push(`intitle:${term}`);
} else if (isWildcard) {
searches = [`intitle:${term.replace(/\?/g, '*')}`, `intitle:"${term.replace(/[*?]/g, '')}"`];
} else {
searches = [`intitle:"${term}"`];
}
const MAX_CONTINUATIONS = 20;
const runSearch = async (srsearch) => {
let cont = {};
let continuations = 0;
let fetched = 0;
try {
do {
const data = await this.api.get({
action: 'query',
list: 'search',
srsearch,
srlimit: 'max',
sroffset: cont.sroffset || 0,
format: 'json'
}, { signal });
const results = data.query?.search || [];
const aarons = results.filter(r => r.title.startsWith('Aaron')).map(r => r.title);
if (aarons.length) {
console.log(`[${srsearch}] offset=${cont.sroffset || 0}`, aarons);
}
for (const p of results) {
const title = p.title.replace(/_/g, ' ').trim();
if (title.startsWith('Aaron')) {
console.log('Checking', title);
console.log({
existing: existing.has(title),
alreadyFound: resultsMap.has(title),
excluded: excludeList.some(e => title.toLowerCase().includes(e)),
matches: this.matchesCriteria(title, compiled)
});
}
if (existing.has(title) || resultsMap.has(title)) continue;
if (excludeList.some(e => title.toLowerCase().includes(e))) continue;
if (this.matchesCriteria(title, compiled)) {
resultsMap.set(title, {
title,
pageid: p.pageid
});
}
if (title.includes('Rahsaan')) {
console.log('Found Rahsaan', title);
}
}
cont = data.continue || {};
continuations++;
} while (cont.sroffset && continuations < MAX_CONTINUATIONS && !signal?.aborted);
} catch (e) {
if (e.name === 'AbortError') {
throw e;
}
console.error(`[Getter] Search error for "${srsearch}":`, e);
}
if (DEBUG) {
console.log(`[Getter] "${srsearch}" → ${fetched} hits, ${resultsMap.size} candidates`);
}
};
for (const srsearch of searches) {
if (signal?.aborted) {
break;
}
await runSearch(srsearch);
}
if (bestAnchor.field === 'surname' && !isWildcard && term.length > 1) {
if (DEBUG) {
console.log(`[Getter] Starting A–Z boost for "${term}"`);
}
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (const letter of alphabet) {
if (signal?.aborted) {
break;
}
await runSearch(`intitle:${letter} intitle:${term}`);
}
}
if (DEBUG) {
console.log(`[Getter] Final candidates for "${term}": ${resultsMap.size}`);
}
return resultsMap;
};
GetterDialog.prototype.formatListWithHeadings = function(items, level, mode, getText) {
if (mode === 'none') return items.map(getText).join('\n');
if (mode === 'smart' && items.length > 0) {
const firstChar = items[0].sortKey.charAt(0).toUpperCase();
if (items.every(i => i.sortKey.charAt(0).toUpperCase() === firstChar)) {
return items.map(getText).join('\n');
}
}
const prefix = '='.repeat(level);
let segments = [];
if (mode === 'alpha') {
const groups = {};
items.forEach(i => {
const char = i.sortKey.charAt(0).toUpperCase();
if (!groups[char]) groups[char] = [];
groups[char].push(i);
});
Object.keys(groups).sort().forEach(char => segments.push({ title: char, items: groups[char] }));
} else {
const count = items.length;
const num = count > 200 ? 26 : (count > 150 ? 3 : 2);
const chunkSize = Math.floor(count / num);
let start = 0;
for (let i = 0; i < num && start < count; i++) {
let end = (i === num - 1) ? count : start + chunkSize;
while (end < count && items[end - 1].sortKey.charAt(0) === items[end].sortKey.charAt(0)) end++;
const chunk = items.slice(start, end);
if (chunk.length) {
const title = this.getSmartHeading(chunk[0], chunk[chunk.length - 1]);
segments.push({ title, items: chunk });
}
start = end;
}
}
return segments.map(s => `${prefix} ${s.title} ${prefix}\n${s.items.map(getText).join('\n')}`).join('\n\n');
};
GetterDialog.prototype.formatResult = function(p, mode, includeIll) {
if (mode === 'anbl') return `* {{anbl|${p.title}}}`;
const bY = p.ent?.claims?.P569?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const dY = p.ent?.claims?.P570?.[0]?.mainsnak?.datavalue?.value?.time?.substring(1, 5);
const bio = bY ? (dY ? ` (${bY}–${dY})` : ` (born ${bY})`) : '';
const rawDesc = (p.desc || '').replace(/\s?\(\s*(?:born|d\.)?\s*\d{4}(?:\s*–\s*\d{4})?\s*\)$/i, '').trim();
const suffix = rawDesc ? `, ${rawDesc}` : '';
let display = `[[${p.title}]]`;
if (includeIll) {
const ill = this.getPreferredSitelink(p.ent);
if (ill) display = `{{ill|${p.title}|${ill.lang}|${ill.title}}}`;
}
return `* ${display}${bio}${suffix}`;
};
GetterDialog.prototype.getActionProcess = function(action) {
if (action === 'generate') return new OO.ui.Process(async () => { await this.runGetter(); });
if (action === 'back') {
if (this.abortController) this.abortController.abort();
return new OO.ui.Process(() => {
this.resultContainer.$element.hide();
this.fieldset.$element.show();
this.actions.setMode('edit');
});
}
if (action === 'exit') { if (this.abortController) this.abortController.abort(); return new OO.ui.Process(() => this.close()); }
return GetterDialog.super.prototype.getActionProcess.call(this, action);
};
GetterDialog.prototype.getBestAnchor = function() {
const getValues = (w) => w.getValue().map(t => t.trim()).filter(Boolean);
const candidates = [...getValues(this.surnameTags).map(t => ({ term: t, field: 'surname', weight: 2000 + this.score(t) })), ...getValues(this.givenTags).map(t => ({ term: t, field: 'given', weight: 1000 + this.score(t) })), ...getValues(this.middleTags).map(t => ({ term: t, field: 'middle', weight: 0 + this.score(t) }))];
return candidates.length === 0 ? null : candidates.reduce((b, c) => (c.weight > b.weight ? c : b));
};
GetterDialog.prototype.getCompiledCriteria = function() { return { given: this.compileCriteria(this.givenTags.getValue()), surname: this.compileCriteria(this.surnameTags.getValue()), middle: this.compileCriteria(this.middleTags.getValue()) }; };
GetterDialog.prototype.getExistingLinks = async function() {
const titles = new Set();
try {
const data = await this.getPageText();
const text = data.parse?.wikitext?.['*'] || '';
const regexes = [/\[\[([^\]|#]+)/g, /\{\{(?:anbl|Annotated biography link)\|([^}|]+)/gi];
regexes.forEach(re => { let m; while ((m = re.exec(text)) !== null) titles.add(this.normalizeTitle(m[1])); });
} catch (e) { console.error('[Getter] Parse error:', e); }
return titles;
};
GetterDialog.prototype.getPageText = function() { return this.pageTextPromise ??= this.api.get({ action: 'parse', page: mw.config.get('wgPageName'), prop: 'wikitext', format: 'json' }); };
GetterDialog.prototype.getPreferredSitelink = function(ent) {
const sitelinks = ent.sitelinks || {};
const key = LANG_PREFS.find(l => sitelinks[l]) || Object.keys(sitelinks).find(k => k.endsWith('wiki') && k !== 'enwiki' && !NON_LANG_WIKIS.includes(k));
return key ? { lang: key.replace(/wiki$/, ''), title: sitelinks[key].title } : null;
};
GetterDialog.prototype.getSetupProcess = function(data) {
return GetterDialog.super.prototype.getSetupProcess.call(this, data).next(() => {
if (data && data.pageTitle) {
if (this.givenTags.getValue().indexOf(data.pageTitle) === -1) {
this.givenTags.addTag(data.pageTitle);
}
}
this.autoPopulateFields();
});
};
GetterDialog.prototype.getSmartHeading = function(startItem, endItem) {
const char1 = startItem.sortKey.charAt(0).toUpperCase();
const char2 = endItem.sortKey.charAt(0).toUpperCase();
return char1 === char2 ? char1 : `${char1}–${char2}`;
};
GetterDialog.prototype.getSortKey = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return parts.length <= 1 ? clean.toLowerCase() : (parts[parts.length - 1] + ', ' + parts.slice(0, -1).join(' ') + ' | ' + clean).toLowerCase();
};
GetterDialog.prototype.initialize = function() {
GetterDialog.super.prototype.initialize.apply(this, arguments);
this.$body.append(this.panel.$element);
this.actions.setMode('edit');
};
GetterDialog.prototype.matchesCriteria = function(title, compiled) {
const parts = this.tokenizeTitle(title);
const check = (val, regs) => regs.length === 0 || regs.some(re => re.test(val));
let givenMatch = check(parts.given, compiled.given);
if (!givenMatch && compiled.given.length > 0) {
const allTokens = this.cleanTitle(title).toLowerCase().split(/\s+/);
givenMatch = compiled.given.some(re => allTokens.some(token => re.test(token)));
}
const surnameMatch = check(parts.surname, compiled.surname);
const middleMatch = compiled.middle.length === 0 || compiled.middle.some(re => parts.middle.some(t => re.test(t)));
if (title.startsWith('Aaron')) {
console.log({
title,
parts,
givenMatch,
surnameMatch,
middleMatch,
compiled
});
}
return givenMatch && surnameMatch && middleMatch;
};
GetterDialog.prototype.normalizeTitle = (t) => { const title = mw.Title.newFromText(t); return title ? title.getPrefixedText() : t.replace(/_/g, ' ').trim(); };
GetterDialog.prototype.processEntityBatch = async function(pageIds, signal) {
const confirmed = new Map();
const pD = await this.api.get({ action: 'query', prop: 'pageprops|description', pageids: pageIds.join('|'), format: 'json' }, { signal });
const qidMap = {},
qidList = [];
Object.values(pD.query.pages).forEach(p => {
if (p.pageprops?.wikibase_item) {
qidList.push(p.pageprops.wikibase_item);
qidMap[p.pageprops.wikibase_item] = p;
}
});
if (!qidList.length) return confirmed;
const entities = await this.fetchEntities(qidList, signal);
for (const qid in entities) {
const ent = entities[qid],
p = qidMap[qid],
isH = ent.claims?.P31?.some(c => c.mainsnak?.datavalue?.value?.id === "Q5");
if ((this.humanOnly.isSelected() && !isH) || (this.excludeHumans.isSelected() && isH)) continue;
confirmed.set(p.title, { title: p.title, desc: p.description || ent.descriptions?.en?.value || '', ent });
}
return confirmed;
};
GetterDialog.prototype.refreshPreviews = function() {
this.previewCache = {};
this.previewPending.clear();
this.previewGeneration++;
this.refreshUI();
};
GetterDialog.prototype.refreshUI = function() {
this.col1.empty();
this.col2.empty();
this.col3.empty();
this.col4.empty();
this.previewElements.clear();
const $ul = $('<ul>').css({ 'list-style-type': 'disc', 'padding-left': '20px', margin: '0' });
this.col4.append($ul);
const fSize = window.GetterConfig.fontSize;
const itemStyle = {
cursor: 'pointer',
padding: '2px',
'font-size': fSize,
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
};
this.items.forEach(item => {
const $w = $('<div>').text(item.wikitext).attr('title', item.wikitext).css({ ...itemStyle, opacity: item.status === 'wikitext' ? 0.3 : 1 });
$w.on('click', () => {
item.status = (item.status === 'wikitext' ? null : 'wikitext');
this.refreshUI();
});
this.col1.append($w);
const $a = $('<div>').text(item.anbl).attr('title', item.anbl).css({ ...itemStyle, opacity: item.status === 'anbl' ? 0.3 : 1 });
$a.on('click', () => {
item.status = (item.status === 'anbl' ? null : 'anbl');
this.refreshUI();
});
this.col3.append($a);
const $li = $('<li>').attr('title', item.page.title).css({
'font-size': fSize,
'margin-bottom': '2px',
'white-space': 'nowrap',
'overflow': 'hidden',
'text-overflow': 'ellipsis'
}).html(this.previewCache[item.page.title] || '<em>Loading...</em>');
$ul.append($li);
this.previewElements.set(item.page.title, $li[0]);
});
this.items.filter(i => i.status).forEach(item => {
const text = item.status === 'wikitext' ? item.wikitext : item.anbl;
this.col2.append($('<div>').text(text).attr('title', text).css({ 'margin-bottom': '4px', 'font-size': fSize }));
});
const missing = this.items.filter(i => !(i.page.title in this.previewCache) && !this.previewPending.has(i.page.title));
if (missing.length) this.batchFetchPreviews(missing);
};
GetterDialog.prototype.renderResults = async function(confirmed) {
this.previewGeneration++;
this.previewPending.clear();
this.items = Array.from(confirmed.values()).map(p => ({
page: p,
sortKey: this.sortBySurname.isSelected() ? this.getSortKey(p.title) : this.cleanTitle(p.title).toLowerCase(),
wikitext: this.formatResult(p, 'wiki', this.includeIll.isSelected()),
anbl: this.formatResult(p, 'anbl', this.includeIll.isSelected()),
status: null
})).sort((a, b) => a.sortKey.localeCompare(b.sortKey, undefined, { sensitivity: 'base' }));
this.refreshUI();
this.fieldset.$element.hide();
this.resultContainer.$element.show();
this.resultControls.$element.show();
this.actions.setMode('result');
};
GetterDialog.prototype.runGetter = async function() {
this.abortController = new AbortController();
this.cachedResults = null;
try {
const best = this.getBestAnchor();
if (!best) { mw.notify('Please enter search terms.', { type: 'error' }); return; }
const existing = this.updateMode.isSelected() ? await this.getExistingLinks() : new Set();
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), this.excludeTags.getValue().map(e => e.trim().toLowerCase()), existing, this.abortController.signal);
const confirmed = new Map(),
pageIds = Array.from(resultsMap.values()).map(p => p.pageid);
for (let i = 0; i < pageIds.length; i += 50) {
const batchConfirmed = await this.processEntityBatch(pageIds.slice(i, i + 50), this.abortController.signal);
batchConfirmed.forEach((val, key) => confirmed.set(key, val));
}
this.cachedResults = confirmed;
this.renderResults(confirmed);
} catch (e) { if (e.name !== 'AbortError') mw.notify('Error generating list.', { type: 'error' }); } finally { this.abortController = null; }
};
GetterDialog.prototype.score = (term) => { const w = (term.match(/[*?]/g) || []).length; return (w === 0 ? 1000 : 0) + term.replace(/[*?]/g, '').length - w; };
GetterDialog.prototype.setPreviewError = function(items) {
items.forEach(item => {
this.previewCache[item.page.title] ??= PREVIEW_ERROR_HTML;
const targetEl = this.previewElements.get(item.page.title);
if (targetEl) targetEl.innerHTML = this.previewCache[item.page.title];
});
};
GetterDialog.prototype.setupUI = function() {
this.givenTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Given names...', allowArbitrary: true });
this.middleTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Middle terms...', allowArbitrary: true });
this.surnameTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Surnames...', allowArbitrary: true });
this.excludeTags = new OO.ui.TagMultiselectWidget({ placeholder: 'Exclude strings...', allowArbitrary: true });
this.humanOnly = new OO.ui.CheckboxInputWidget({ selected: true });
this.excludeHumans = new OO.ui.CheckboxInputWidget({ selected: false });
this.includeIll = new OO.ui.CheckboxInputWidget({ selected: false });
this.sortBySurname = new OO.ui.CheckboxInputWidget({ selected: true });
this.updateMode = new OO.ui.CheckboxInputWidget({ selected: false });
this.mode = new OO.ui.RadioSelectInputWidget({
options: [{ data: 'none', label: 'No headings' }, { data: 'smart', label: 'Smart grouping' }, { data: 'alpha', label: 'A-Z' }],
value: 'none'
});
this.level = new OO.ui.RadioSelectInputWidget({
options: [{ data: 2, label: 'Level 2' }, { data: 3, label: 'Level 3' }, { data: 4, label: 'Level 4' }],
value: 3
});
if (!this.level || !this.mode) {
console.error('Level or mode widget missing during UI setup');
}
mw.util.addCSS(` .getter-result-fieldset .oo-ui-fieldsetLayout-header {
font-size: 13px !important;
color: #444;
}
.getter-formatting-container {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 40px;
}
.getter-formatting-container .oo-ui-fieldLayout {
display: flex;
align-items: center;
margin-top: 2px;
}
.getter-formatting-container .oo-ui-fieldLayout-label {
margin-right: 12px;
font-weight: bold;
width: auto;
flex-shrink: 0;
line-height: 1.4;
}
.oo-ui-radioSelectInputWidget {
vertical-align: middle;
}
`);
this.resultControls = new OO.ui.FieldsetLayout({ label: 'Output Formatting', classes: ['getter-result-fieldset'] });
const $formattingRow = $('<div>').addClass('getter-formatting-container');
const modeLayout = new OO.ui.FieldLayout(this.mode, {
label: 'Grouping Mode',
align: 'top'
});
const levelLayout = new OO.ui.FieldLayout(this.level, {
label: 'Heading Level',
align: 'top'
});
$formattingRow.append(modeLayout.$element, levelLayout.$element);
this.resultControls.$element.append($formattingRow);
this.fieldset = new OO.ui.FieldsetLayout({ label: 'Advanced Filter Settings' });
this.fieldset.addItems([
new OO.ui.FieldLayout(this.givenTags, { label: 'Given names', align: 'top' }),
new OO.ui.FieldLayout(this.middleTags, { label: 'Middle terms', align: 'top' }),
new OO.ui.FieldLayout(this.surnameTags, { label: 'Surnames', align: 'top' }),
new OO.ui.FieldLayout(this.excludeTags, { label: 'Exclude strings', align: 'top' }),
new OO.ui.FieldLayout(this.humanOnly, { label: 'Humans only', align: 'inline' }),
new OO.ui.FieldLayout(this.excludeHumans, { label: 'Exclude humans', align: 'inline' }),
new OO.ui.FieldLayout(this.sortBySurname, { label: 'Sort by surname', align: 'inline' }),
new OO.ui.FieldLayout(this.updateMode, { label: 'Exclude existing links', align: 'inline' }),
new OO.ui.FieldLayout(this.includeIll, { label: 'Include {{ill}}', align: 'inline' })
]);
const colStyle = { height: '480px', 'overflow-y': 'auto', 'overflow-x': 'hidden', border: '1px solid #ccc', padding: '10px', 'white-space': 'nowrap', 'text-overflow': 'ellipsis' };
this.col1 = $('<div>').css(colStyle);
this.col2 = $('<div>').css({ ...colStyle, background: '#f9f9f9' });
this.col3 = $('<div>').css(colStyle);
this.col4 = $('<div>').css(colStyle);
const columns = [this.col1, this.col2, this.col3, this.col4];
columns.forEach(col => {
col.on('scroll', (e) => {
const scrollTop = e.target.scrollTop;
columns.forEach(otherCol => { if (otherCol[0] !== e.target && otherCol[0].scrollTop !== scrollTop) otherCol[0].scrollTop = scrollTop; });
});
});
const $refreshBtn = new OO.ui.ButtonWidget({ label: 'Refresh', icon: 'reload', flags: ['progressive'] });
$refreshBtn.on('click', () => this.refreshPreviews());
this.resultContainer = new OO.ui.PanelLayout({ padded: true, expanded: true });
const $columns = $('<div>').css({ display: 'flex', gap: '10px' });
const createHeader = (label) => new OO.ui.LabelWidget({ label: label }).$element.css({ 'font-weight': 'bold', 'font-size': '13px', 'margin-bottom': '5px' });
$columns.append($('<div>').css('flex', '1.2').append(createHeader('Wikitext'), this.col1, this.createButtonRow('wikitext')), $('<div>').css('flex', '1.2').append(createHeader('Chosen List'), this.col2, this.createButtonRow('list')), $('<div>').css('flex', '0.8').append(createHeader('Anbl'), this.col3, this.createButtonRow('anbl')), $('<div>').css('flex', '1.2').append(createHeader('Anbl Preview'), this.col4, $('<div>').css('margin-top', '10px').append($refreshBtn.$element)));
this.resultContainer.$element.append($columns, $('<div>').css('margin-top', '20px').append(this.resultControls.$element)).hide();
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.panel.$element.append(this.fieldset.$element, this.resultContainer.$element);
};
GetterDialog.prototype.silentScan = async function() {
if (window.GetterConfig.enableSilentScan === false || !/(?:list of|surname|given name)/i.test(mw.config.get('wgTitle'))) return;
$('.mw-notification[data-notification-tag="getter-discovery"]').remove();
await this.ensureFieldsInitialized();
const best = this.getBestAnchor();
if (!best) return;
const resultsMap = await this.findCandidates(best, this.getCompiledCriteria(), [], await this.getExistingLinks(), null);
if (!resultsMap.size) return;
const confirmed = await this.processEntityBatch(Array.from(resultsMap.values()).map(p => p.pageid).slice(0, 50), null);
if (confirmed.size > 0) {
this.cachedResults = confirmed;
const $link = $('<a>', { text: 'Review', href: '#', style: 'font-weight:bold; cursor:pointer' });
$link.on('click', (e) => {
e.preventDefault();
$link.closest('.mw-notification').remove();
this.windowManager.openWindow(this);
this.renderResults(this.cachedResults);
});
mw.notify($('<span>').text(`Getter found ${resultsMap.size > 50 ? '50+' : confirmed.size} potential entries. `).append($link), { tag: 'getter-discovery', autoHide: false });
}
};
GetterDialog.prototype.tokenizeTitle = function(title) {
const clean = this.cleanTitle(title);
const parts = clean.split(/\s+/);
return {
given: parts[0] || '',
surname: parts.length > 1 ? parts[parts.length - 1] : '',
middle: parts.length > 2 ? parts.slice(1, -1) : []
};
};
// --- Initialization ---
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs-ui', 'oojs-ui-widgets', 'oojs-ui-windows']).then(() => {
const wm = new OO.ui.WindowManager();
$(document.body).append(wm.$element);
const dialog = new GetterDialog({ size: 'full' });
dialog.windowManager = wm;
wm.addWindows([dialog]);
$(mw.util.addPortletLink('p-tb', '#', 'Getter', 't-getter')).on('click', (e) => {
e.preventDefault();
wm.openWindow(dialog);
});
});
}(mediaWiki, jQuery, OO));
(function() {
if (window.IllWill) return;
const STRIKEOUT = true;
const HIDE = 'hide';
const IllWill = {
Version: "0.5",
Attribution: 'created by <a target="_blank" href="/wiki/User:Cobaltcigs">cobaltcigs</a>',
Summary: "Added interlanguage links using IllWill.js",
DataApi: "https://www.wikidata.org/w/api.php",
LocalLang: mw.config.get("wgPageContentLanguage"),
Disambiguator: / \(.*\)$/,
SiteLinks: {},
Config: {
MaxLanguages: 10,
IgnoreScientific: STRIKEOUT,
AutoDiff: true
},
css: `
#illwill { clear: both; margin-bottom: 1.5em; border: 1px solid #a2a9b1; padding: 15px; background: #f8f9fa; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.ill-sitelinks a { display: inline-block; margin-bottom: 3px; font-size: 0.9em; color: #36c; }
.ill-size-tag { font-size: 0.8em; color: #72777d; margin-left: 6px; white-space: nowrap; }
.illwill-ignore { text-decoration: line-through; color: #72777d; font-size: 0.85em; }
.ill-x { width: 35px; text-align: center; }
.ill-input, .ill-output { font-family: 'Courier New', monospace; white-space: pre-wrap; font-size: 0.9em; background: #fff; border: 1px solid #eaecf0; padding: 4px; border-radius: 2px; }
#ill-buttons { text-align: right; padding: 10px; border-top: 1px solid #a2a9b1; margin-top: 10px; }
#ill-buttons button { margin-left: 10px; cursor: pointer; padding: 6px 16px; border-radius: 2px; border: 1px solid #a2a9b1; transition: all 0.2s; }
#ill-ok { background: #36c; color: #fff; border-color: #36c !important; font-weight: bold; }
#ill-ok:hover { background: #447ff5; }
#ill-ok:disabled { background: #eaecf0; color: #72777d; border-color: #c8ccd1 !important; cursor: not-allowed; opacity: 0.7; }
#ill-table { width: 100%; border-collapse: collapse; margin-top: 10px; background: white; }
#ill-table th { background: #f2f2f2; text-align: left; padding: 8px; border: 1px solid #a2a9b1; }
#ill-table td { padding: 8px; border: 1px solid #a2a9b1; vertical-align: top; }
#ill-table caption { background: #eaffea; font-weight: bold; padding: 10px; border: 1px solid #a2a9b1; border-bottom: none; font-size: 1.1em; }
#ill-help { font-size: 0.85em; color: #54595d; padding: 10px; line-height: 1.4; }
.ill-loading-inline { font-style: italic; color: #72777d; font-size: 0.9em; display: block; margin: 5px 0; }
`,
setup() {
if (!["edit", "submit"].includes(mw.config.get('wgAction'))) return;
mw.loader.addStyleTag(this.css);
const link = mw.util.addPortletLink('p-cactions', '#', 'IllWill', 'ca-illwill', "Add interlanguage links via Wikidata");
if (link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.makePanel();
});
}
},
norm(s) {
if (!s) return "";
const t = s.trim().replace(/[\s_]+/g, " ");
return t.charAt(0).toUpperCase() + t.slice(1);
},
async getRedLinks($textbox) {
const txt = $textbox.textSelection('getContents') || "";
const wl = txt.match(/\[\[[^[\]\n]+\]\]/g);
if (!wl) return {
reds: [],
txt
};
const api = new mw.Api();
const textToParse = wl.join("").replace(/\|[^\]]+/g, "");
try {
const data = await api.post({
action: 'parse',
text: textToParse,
contentmodel: 'wikitext',
formatversion: 2
});
const tempDiv = document.createElement('div');
tempDiv.innerHTML = data.parse.text;
const reds = Array.from(tempDiv.querySelectorAll("a.new")).map(x => {
const urlParams = new URLSearchParams(x.href.split('?')[1]);
return urlParams.get('title')?.replace(/_/g, " ");
}).filter(Boolean);
return {
reds: [...new Set(reds)],
txt
};
} catch (e) {
return {
reds: [],
txt
};
}
},
async makePanel() {
const $editform = $('#editform');
const $textbox = $('#wpTextbox1');
$('#illwill').remove();
$editform.hide();
const $wrapper = $('<div id="illwill">Loading redlinks...</div>').insertBefore($editform);
const {
reds,
txt
} = await this.getRedLinks($textbox);
const wtLinks = [];
reds.forEach(title => {
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s/g, "[\\s_]+");
const re = new RegExp("\\[\\[\\s*" + escaped + "\\s*(?:\\|[^\\[\\]]+)?\\]\\]", "ig");
const match = txt.match(re);
if (match && !wtLinks.some(z => z.title === title)) {
wtLinks.push({
link: match[0],
title
});
}
});
if (!wtLinks.length) {
const $back = $('<button>Quit</button>').on('click', () => {
$wrapper.remove();
$editform.show();
});
$wrapper.empty().append($('<strong>No valid redlinks found in text.</strong> '), $back);
return;
}
const $table = $(`
<table id="ill-table" class="wikitable">
<caption>IllWill.js (v${this.Version})</caption>
<thead>
<tr>
<th class="ill-x"><input type="checkbox" id="ill-select-all" disabled></th>
<th>Input link</th>
<th>Possible Wikidata matches</th>
<th>Sitelinks (top ${this.Config.MaxLanguages} by size)</th>
<th>Resulting wikitext</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td colspan="3" id="ill-help">Carefully examine possible Wikidata matches.</td>
<td colspan="2" id="ill-buttons">
<button id="ill-cancel">Cancel</button>
<button id="ill-ok" disabled>Show changes</button>
</td>
</tr>
</tfoot>
</table>
`);
const $tbody = $table.find('tbody');
wtLinks.forEach((item, i) => {
$tbody.append(`
<tr id="ill-row-${i}" data-index="${i}">
<td class="ill-x"><input type="checkbox" class="ill-row-check" disabled></td>
<td class="ill-input">${this.escapeHTML(item.link)}</td>
<td class="ill-wd-item"><span class="ill-loading-inline">Searching...</span></td>
<td class="ill-sitelinks"></td>
<td class="ill-output"></td>
</tr>
`);
});
$wrapper.empty().append($table);
const chunkSize = 5;
for (let i = 0; i < wtLinks.length; i += chunkSize) {
const chunk = wtLinks.slice(i, i + chunkSize);
await Promise.all(chunk.map((item, idx) => this.fetchWikidataOptimized(item.title, i + idx)));
}
$table.on('click', '#ill-cancel', () => {
$editform.show();
$wrapper.remove();
});
$table.on('click', '#ill-ok', () => this.onOkButton($editform, $wrapper, $textbox));
$table.on('change', '#ill-select-all', (e) => {
const isChecked = $(e.target).is(':checked');
$table.find('.ill-row-check:not(:disabled)').each(function() {
$(this).prop('checked', isChecked).trigger('change');
});
});
$table.on('change', '.ill-row-check', (e) => {
this.onCheckbox(e);
this.updateOkButtonState($table);
});
$table.on('change', 'input[type="radio"]', (e) => {
this.showSiteLinks(e);
});
},
async fetchWikidataOptimized(title, index) {
const query = title.replace(this.Disambiguator, "");
const url = `${this.DataApi}?action=wbsearchentities&search=${encodeURIComponent(query)}&language=${this.LocalLang}&limit=5&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
const results = data.search;
const $cell = $(`#ill-row-${index} .ill-wd-item`);
if (!results || !results.length) {
$cell.html('<span class="ill-na">(no results)</span>');
return;
}
const entityIds = results.map(r => r.id);
await this.fetchEntities(entityIds, index, $cell);
} catch (e) {
$(`#ill-row-${index} .ill-wd-item`).text("Search failed.");
}
},
async fetchEntities(ids, rowIndex, $cell) {
const url = `${this.DataApi}?action=wbgetentities&ids=${ids.join('|')}&props=labels|descriptions|claims|sitelinks&format=json&origin=*`;
try {
const resp = await fetch(url);
const data = await resp.json();
$cell.empty();
ids.forEach(qNum => {
const entity = data.entities[qNum];
if (!entity) return;
const isScientific = entity.claims?.P31?.some(s => s.mainsnak?.datavalue?.value?.id === "Q13442814");
if (isScientific && this.Config.IgnoreScientific === HIDE) return;
const desc = entity.descriptions?.[this.LocalLang]?.value || entity.descriptions?.en?.value || "";
const label = entity.labels?.[this.LocalLang]?.value || entity.labels?.en?.value || qNum;
const isStrikeout = isScientific && this.Config.IgnoreScientific === STRIKEOUT;
const $div = $(`
<div class="${isStrikeout ? 'illwill-ignore' : ''}">
<input type="radio" name="selection-${rowIndex}" value="${qNum}" id="radio-${qNum}" ${isStrikeout ? 'disabled' : ''}>
<label for="radio-${qNum}">
<a href="https://wikidata.org/wiki/${qNum}" target="_blank">${this.escapeHTML(label)}</a>:
<span class="ill-desc">${desc ? this.escapeHTML(desc) : '<i>no description</i>'}</span>
</label>
</div>
`);
$cell.append($div);
this.SiteLinks[qNum] = entity.sitelinks || {};
});
} catch (e) {
$cell.text("Detail fetch failed.");
}
},
updateOkButtonState($table) {
const $rows = $table.find('.ill-row-check:not(:disabled)');
const $checkedRows = $rows.filter(':checked');
const anyEnabled = $rows.length > 0;
$table.find('#ill-ok').prop('disabled', $checkedRows.length === 0);
const $master = $table.find('#ill-select-all');
$master.prop('disabled', !anyEnabled);
if (anyEnabled && $rows.length === $checkedRows.length) {
$master.prop('checked', true);
} else {
$master.prop('checked', false);
}
},
async showSiteLinks(e) {
const isManualToggling = e.fromCheckbox || false;
const qNum = e.target.value;
const $row = $(e.target).closest('tr');
const sitelinks = this.SiteLinks[qNum];
const $checkbox = $row.find('.ill-row-check');
const $sitelinkCell = $row.find('.ill-sitelinks');
const $outputCell = $row.find('.ill-output');
const $table = $('#ill-table');
const effectiveMax = Math.min(Math.max(this.Config.MaxLanguages, 3), 6);
const validKeys = Object.keys(sitelinks).filter(k => k.endsWith('wiki') && !k.includes('commons'));
$sitelinkCell.html('<span class="ill-loading-inline">Measuring articles...</span>');
$outputCell.empty();
if (!isManualToggling) {
$checkbox.prop('disabled', true).prop('checked', false);
}
const candidates = validKeys.slice(0, 25);
const sizeMap = [];
await Promise.all(candidates.map(async (key) => {
const lang = key.replace('wiki', '').replace(/_/g, '-');
const title = sitelinks[key].title;
const endpoint = `https://${lang}.wikipedia.org/w/api.php?action=query&titles=${encodeURIComponent(title)}&prop=info&format=json&origin=*`;
try {
const res = await fetch(endpoint).then(r => r.json());
const page = Object.values(res.query.pages)[0];
sizeMap.push({
lang,
title,
length: page.length || 0
});
} catch (err) {
sizeMap.push({
lang,
title,
length: 0
});
}
}));
sizeMap.sort((a, b) => b.length - a.length);
const topLinks = sizeMap.slice(0, effectiveMax);
$sitelinkCell.html(topLinks.map(item => {
const kbValue = Math.round(item.length / 1024);
return `<div><a href="https://${item.lang}.wikipedia.org/wiki/${encodeURIComponent(item.title)}" target="_blank">${item.lang}:${this.escapeHTML(item.title)}</a> <span class="ill-size-tag">(${kbValue} kB)</span></div>`;
}).join(""));
const inputWikitext = $row.find('.ill-input').text();
const match = inputWikitext.match(/\[\[(.*?)\]\]/);
if (match) {
const parts = match[1].split('|');
const targetPage = parts[0].trim();
const surfaceName = (parts[1] || parts[0]).trim();
const illLangs = topLinks.map(item => {
const isSameTitle = this.norm(item.title) === this.norm(targetPage);
return `|${item.lang}|${isSameTitle ? '' : item.title}`;
}).join("");
const ltParam = (this.norm(surfaceName) === this.norm(targetPage)) ? "" : `|lt=${surfaceName}`;
$outputCell.text(`{{ill|${targetPage}${ltParam}${illLangs}}}`);
}
$checkbox.prop('disabled', !topLinks.length);
if (!isManualToggling) {
$checkbox.prop('checked', !!topLinks.length);
}
this.updateOkButtonState($table);
},
onCheckbox(e) {
const $row = $(e.target).closest('tr');
const $radio = $row.find('input[type="radio"]:checked');
const isChecked = $(e.target).is(':checked');
if (isChecked) {
if ($radio.length) {
this.showSiteLinks({
target: $radio[0],
fromCheckbox: true
});
}
} else {
$row.find('.ill-sitelinks, .ill-output').empty();
}
},
onOkButton($editform, $wrapper, $textbox) {
let text = $textbox.textSelection('getContents');
let changed = false;
$wrapper.find('.ill-row-check:checked').each(function() {
const $row = $(this).closest('tr');
const input = $row.find('.ill-input').text();
const output = $row.find('.ill-output').text();
if (output) {
const escapedInput = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
text = text.replace(new RegExp(escapedInput, "gi"), output);
changed = true;
}
});
if (changed) {
$('#wpSummary').val((i, v) => (v ? v + "; " : "") + this.Summary);
$textbox.textSelection('setContents', text);
$wrapper.remove();
$editform.show();
if (this.Config.AutoDiff) $('#wpDiff').click();
}
},
escapeHTML(str) {
return str.replace(/[&<>"']/g, m => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
})[m]);
}
};
window.IllWill = IllWill;
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery.textSelection'], () => {
$(() => IllWill.setup());
});
})();
/* global ComradeLib, mw, $ */
(async function() {
try {
await mw.loader.getScript('https://test.wikipedia.org/w/index.php?title=User:Sam_Sailor/test.js/comrade-options.js&action=raw&ctype=text/javascript');
} catch (e) {
console.warn("Comrade: Could not load /comrade-options.js, using fallback defaults.", e);
}
window.ComradeOptions = Object.assign({
version: "0.5.0",
excludedTemplates: [],
nudgeTimeout: 10000,
debugMode: false
}, window.ComradeOptions || {});
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 res = await fetch(`https://www.wikidata.org/w/api.php?action=wbgetentities&ids=${qid}&props=claims|descriptions|labels&languages=en&format=json&origin=*`).then(r => r.json());
return res.entities[qid];
}
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;
ComradeLib.checkLeadCapitalization(currentTitle);
// 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);
await ComradeLib.checkNoShortDesc(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 isRedirect = !!mw.config.get("wgIsRedirect");
const userExclusions = window.comradeQualityExclusions || [];
const allExclusions = [...window.ComradeOptions.excludedTemplates, ...userExclusions];
const exclusionRegexString = allExclusions.map(t => t.replace("Template:", "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
const exclusionRegex = new RegExp(exclusionRegexString, 'i');
if (isRedirect || exclusionRegex.test(wikitext)) {
console.log("Comrade check skipped: Page is a redirect or excluded via options.");
return;
}
ComradeLib.checkQualityConsistency();
} catch (err) {
console.error("Comrade error:", err);
}
}
init();
})();
(function() {
'use strict';
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Search') {
return;
}
mw.loader.using(['mediawiki.util']).then(function() {
$(function() {
const $resultsInfo = $('.results-info');
const totalResultsRaw = $resultsInfo.attr('data-mw-num-results-total') || '0';
function formatWithCommas(numberString) {
return numberString.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
const state = {
language: mw.config.get('wgContentLanguage'),
totalResultsFormatted: formatWithCommas(totalResultsRaw)
};
let titlesCache = null;
function extractResultTitles() {
const currentResults = $('.mw-search-result-heading');
if (!titlesCache || titlesCache.length !== currentResults.length) {
titlesCache = currentResults.map(function() {
return $(this).find('a:first').attr('title');
}).get().filter(Boolean);
}
return titlesCache;
}
mw.hook("wikipage.content").add(function() {
titlesCache = null;
});
function addLimitOptions() {
const selects = document.querySelectorAll('.cdx-table-pager__limit-form select[name="limit"]');
if (!selects.length) return;
const valuesToAdd = ["1000", "2500", "5000"];
selects.forEach(function(select) {
valuesToAdd.forEach(function(value) {
if (select.querySelector('option[value="' + value + '"]')) return;
const option = document.createElement("option");
option.value = value;
option.textContent = formatWithCommas(value) + " items";
const existingOptions = Array.from(select.querySelectorAll("option"));
const insertBefore = existingOptions.find(function(opt) {
return Number(opt.value) > Number(value);
});
if (insertBefore) {
select.insertBefore(option, insertBefore);
} else {
select.appendChild(option);
}
});
});
}
mw.hook("wikipage.content").add(addLimitOptions);
const numLinks = $('.mw-numlink');
if (numLinks.length > 4 && !numLinks.eq(4).next().is('a[href*="limit=5000"]')) {
const $firstAnchor = numLinks.eq(4);
const $firstSpan = $('<span> | </span>').insertAfter($firstAnchor);
const url1 = new URL(window.location.href);
url1.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url1.toString()
}).insertAfter($firstAnchor).before($firstSpan);
}
if (numLinks.length > 9 && !numLinks.eq(9).next().is('a[href*="limit=5000"]')) {
const $secondAnchor = numLinks.eq(9);
const $secondSpan = $('<span> | </span>').insertAfter($secondAnchor);
const url2 = new URL(window.location.href);
url2.searchParams.set('limit', '5000');
$('<a>', {
text: '5000',
title: 'Show 5000 results per page',
href: url2.toString()
}).insertAfter($secondAnchor).before($secondSpan);
}
const $container = $("<div></div>").addClass("enhanced-search-container").hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const interwikiResults = document.getElementById("mw-interwiki-results");
if (interwikiResults) {
$container.addClass("enhanced-search-floating").insertBefore("#mw-interwiki-results");
} else {
$container.addClass("enhanced-search-absolute").insertBefore(".searchresults");
}
const totalFormattedStr = state.totalResultsFormatted;
const $titleElement = $("<div>Show a shortened list of<br />this page's " + $('.mw-search-result-heading').length + " results <small>(of total of " + totalFormattedStr + ")</small></div>").addClass("enhanced-header").appendTo($container);
const $contentArea = $("<div></div>").addClass("enhanced-content-area").appendTo($container).hide();
const $note1 = $("<div>Copy this page\'s " + $('.mw-search-result-heading').length + " results to the clipboard as...</div>").addClass("enhanced-note").insertAfter($container).hide();
function setupHoverTooltip(element, message) {
$(element).attr('title', message).hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
}
const $note2 = $("<div>...plain text<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note1).hide();
setupHoverTooltip($note2, 'This button copies this page\'s results to the clipboard as plain text, i.e. without wikitext markup. Since this copies only the results shown on this search page, make sure you have clicked the "view 20, 50, 100, 250, 500, 5000" link. This format is compatible with PetScan and AutoWikiBrowser (AWB) for example.');
const $note3 = $("<div>...wikitext<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note2).hide();
setupHoverTooltip($note3, 'This button copies this page\'s results to the clipboard and also adds wikitext markup.');
const $note4 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note3).hide();
setupHoverTooltip($note4, 'This button copies a PetScan URL with this page\'s results to the clipboard. Note that if the PetScan URL becomes too long (~250+ pages), the URL might not work.');
const $note5 = $("<div>...or this search query's total results of " + totalFormattedStr + "<sup> (?)</sup> as...</div>").addClass("enhanced-note").insertAfter($note4).hide().attr('title', 'Note that the actual limit is 10,000, which is MediaWiki\'s maximum limit.').hover(function() {
$(this).addClass("hover");
}, function() {
$(this).removeClass("hover");
});
const $note6 = $("<div>...PetScan URL<sup> (?)</sup></div>").addClass("enhanced-btn").insertAfter($note5).hide();
setupHoverTooltip($note6, 'This button copies a PetScan URL with all ' + totalFormattedStr + ' results to the clipboard. Note that the maximum limit is 10,000 due to MediaWiki\'s restriction.');
const $note7 = $("<div><sup>(?)</sup> = hover over for tooltip details</div>").addClass("enhanced-note-right").insertAfter($note6).hide();
const noteElements = [$note1, $note2, $note3, $note4, $note5, $note6, $note7];
if (interwikiResults !== null) {
noteElements.forEach($el => $el.addClass("enhanced-floating-elements"));
} else {
noteElements.forEach(function($el, index) {
$el.addClass("enhanced-absolute-elements enhanced-position-" + index);
});
}
function formatPlain() {
return extractResultTitles().join("\r\n");
}
function formatWikitext() {
const titles = extractResultTitles().map(title => "* [[" + title + "]]");
const header = "This is a list of [" + window.location.href + " these search results] (made with the script [[User:JAAqqO/EnhancedSearchPage|EnhancedSearchPage]]):\r\n";
return header + titles.join("\r\n");
}
function formatPetScan() {
const encodedTitles = extractResultTitles().map(encodeURIComponent).join("%0A");
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&manual_list=" + encodedTitles + "&manual_list_wiki=" + state.language + "wiki&project=wikipedia&language=" + state.language + "&doit=";
}
function formatPetScanTotal() {
const oouiInput = document.getElementById("ooui-php-1");
const value = oouiInput ? oouiInput.value : "";
if (value) {
return "https://petscan.wmflabs.org/?active_tab=tab_other_sources&ns%5B0%5D=1&search_query=" + encodeURIComponent(value) + "&search_wiki=" + state.language + "wiki&search_max_results=10000&project=wikipedia&language=" + state.language + "&doit=";
}
return "";
}
function copyToClipboard(text) {
if (!text) return;
if (!navigator.clipboard) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
} catch (e) {
console.error('Fallback copy failed', e);
}
textarea.remove();
} else {
navigator.clipboard.writeText(text).catch(function(err) {
console.error("Copy failed: ", err);
});
}
}
function showCopiedMessage(event) {
const posX = event.pageX;
const posY = event.pageY;
const $msg = $("<div class='copied-message'>Copied!</div>").appendTo("body");
$msg.css({
position: "absolute",
top: (posY + 10) + "px",
left: (posX + 10) + "px",
backgroundColor: "#f0f0f0",
border: "1px solid grey",
padding: "5px 10px",
borderRadius: "5px",
zIndex: 9999
});
setTimeout(function() {
$msg.fadeOut("slow", function() {
$(this).remove();
});
}, 500);
}
function performButtonAnimation($element) {
$element.css("transform", "scaleY(0.9)").animate({
opacity: '0.5'
}, "fast", function() {
$(this).animate({
opacity: '1'
}, "slow").css("transform", "scaleY(1)");
});
}
let isExpanded = false;
function showContentArea() {
if (isExpanded) return;
isExpanded = true;
const currentResults = $('.mw-search-result-heading');
$contentArea.empty().append(currentResults.clone());
$contentArea.slideDown();
$(".searchalttitle").remove();
$container.addClass("enhanced-active-container");
$titleElement.remove();
noteElements.forEach($el => $el.show());
}
function bindCopyAction($element, formatterFn) {
$element.click(function(event) {
copyToClipboard(formatterFn());
showCopiedMessage(event);
performButtonAnimation($(this));
});
}
$container.click(showContentArea);
bindCopyAction($note2, formatPlain);
bindCopyAction($note3, formatWikitext);
bindCopyAction($note4, formatPetScan);
bindCopyAction($note6, formatPetScanTotal);
const $pageDown = $("<div><br /><br />[ ↓ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the bottom of the page');
}, function() {
$(this).removeClass("hover");
}).insertBefore('.mw-search-result:eq(0)');
const $pageUp = $("<div>[ ↑ ]</div>").addClass("enhanced-scroll-btn").hover(function() {
$(this).addClass("hover");
$(this).attr('title', 'Go to the top of the page');
}, function() {
$(this).removeClass("hover");
}).insertAfter('.mw-search-result:eq(-1)');
$pageDown.click(function(event) {
event.preventDefault();
$('html, body').animate({
scrollTop: $(document).height()
}, 500);
});
$pageUp.click(function() {
$('html, body').animate({
scrollTop: 0
}, 500);
});
$("<span> (<b>" + $('.mw-search-result-heading').length + "</b> shown on the page)</span>").appendTo('.results-info');
const searchDidYouMean = document.querySelector('.searchdidyoumean');
if (searchDidYouMean) {
searchDidYouMean.style.color = '#b32424';
searchDidYouMean.style.fontSize = '20px';
}
});
});
})();
//</nowiki>
lx1xf8j7nc1t7lp04g8pd7fvpznskgu
Nicki Minaj
0
100967
769842
753474
2026-10-23T18:58:06Z
InternetArchiveBot
34092
Rescuing 14 sources and tagging 0 as dead.) #IABot (v2.0.9.6
769842
wikitext
text/x-wiki
{{Short description|Trinidadian rapper and singer (born 1982)}}
{{pp-blp|small=yes}}
{{Use American English|date=April 2017}}
{{Use mdy dates|date=December 2023}}
{{Infobox person
| name = Nicki Minaj
| image = Nicki Minaj 2025 (3x4 cropped).jpg
| caption = Minaj in 2025
| alt = Head shot of Minaj
| birth_name = Onika Tanya Maraj
| birth_date = {{Birth date and age|1982|12|8}}<!-- - SOURCED IN EARLY LIFE - -->
| birth_place = [[Port of Spain]], Trinidad and Tobago
| occupation = {{Flatlist|
* Rapper
* singer
* songwriter
}}
| years_active = 2002–present<!--Sourced in career beginnings section-->
| works = {{Flatlist|
* [[Nicki Minaj discography|Discography]]
* [[List of songs recorded by Nicki Minaj|songs]]
* [[Nicki Minaj videography|videography]]
* [[List of Nicki Minaj live performances|performances]]
}}
| spouse = {{marriage|Kenneth Petty|2019}}
| children = 1
| awards = [[List of awards and nominations received by Nicki Minaj|Full list]]
| module = {{Infobox musical artist|embed=yes
| background = solo_singer
| genre = {{Flatlist|
* [[Hip-hop]]
* [[Pop music|pop]]
* [[Contemporary R&B|R&B]]<!--Do NOT add citations, these genres are cited in the Musical style section. Do not add more genres to the infobox; genres listed here are the most commonly cited by reliable sources for an artist's overall discography. Source other genres in the Musical style section. -->
}}
| origin = New York City, US<!--Do not link NYC or change to "U.S" per the template guidelines-->
| label = {{flatlist|
* [[Republic Records|Republic]]
* Heavy On It
* [[Young Money Entertainment|Young Money]]
* [[Cash Money Records|Cash Money]]
}}
| website = {{URL|nickiminajofficial.com}}
{{Infobox
| child=yes
| header = Logo
| headerstyle = background:#b0c4de
| data1 = }}
}}
| signature = Nicki Minaj sig.svg
}}
'''Onika Tanya Maraj-Petty''' (born December 8, 1982), known professionally as '''Nicki Minaj''' ({{IPAc-en|'|n|ɪ|k|i|_|m|ᵻ|ˈ|n|ɑː|ʒ|audio=LL-Q1860 (eng)-Flame, not lame-Nicki Minaj.wav}} {{respell|NIK|ee|_|min|AHZH}}), is a Trinidadian rapper, singer, and songwriter.<!--only the most notable occupations in lead per [[MOS:ROLEBIO]]--> Dubbed the "[[Queen of Rap]]" and one of the most influential rappers of all time,<!--this is supported by multiple sources in the cultural impact section--> she is noted for her dynamic [[Flow (rapping)|rap flow]], witty lyrics, musical versatility, and [[alter ego]]s, and is credited as a driving force in the mainstream resurgence of female rap since the 2010s.<!--this is supported by multiple sources in the cultural impact section-->
Raised in New York City, Minaj began rapping professionally in the early 2000s and gained recognition with her [[Nicki Minaj discography#Mixtapes|three mixtapes]] between 2007 and 2009. Minaj's debut studio album, ''[[Pink Friday]]'' (2010), opened with the largest female rap album sales week of the 21st century, topped the US [[Billboard 200|''Billboard'' 200]], and spawned the single "[[Super Bass]]". She explored [[dance-pop]] on her second US number-one album, ''[[Pink Friday: Roman Reloaded]]'' (2012), which produced the top-five single, "[[Starships (song)|Starships]]". She returned to her [[hip-hop]] roots with ''[[The Pinkprint]]'' (2014) and ''[[Queen (Nicki Minaj album)|Queen]]'' (2018), which yielded the singles "[[Anaconda (song)|Anaconda]]" and "[[Chun-Li (Nicki Minaj song)|Chun-Li]]". Minaj achieved her first [[Billboard Hot 100|''Billboard'' Hot 100]] number-one singles with the 2020 duets "[[Say So#Remix|Say So]]" and "[[Trollz (song)|Trollz]]"; the former was the first female rap collaboration to top the chart. Her fifth album, ''[[Pink Friday 2]]'' (2023), made her the female rapper with the most US number-one albums (three) and spawned her first solo US number-one single, "[[Super Freaky Girl]]". Its [[Pink Friday 2 World Tour|concert tour]] became the highest-grossing by a female rapper.
Minaj is one of the [[List of best-selling music artists|world's best-selling music artists]] and the [[List of best-selling female rappers|best-selling female rapper]], with over 100 million records sold.<ref name="post"/> She has over 54 million certified singles sold in the US, three [[List of highest-certified digital singles in the United States|diamond-certified]] singles, and in 2024 became the first female rapper with multiple diamond-certified solo songs (two) by the [[RIAA]]. In 2023, ''[[Billboard (magazine)|Billboard]]'' and ''[[Vibe (magazine)|Vibe]]'' ranked Minaj as [[Billboard and Vibe's 50 Greatest Rappers of All Time#Top 10 rappers|the greatest female rapper of all time]]. Her various [[List of awards and nominations received by Nicki Minaj|accolades]] include a [[Brit Awards|Brit Award]], five [[Billboard Music Awards|''Billboard'' Music Awards]], nine [[American Music Awards]], eight [[MTV Video Music Awards]] (including the [[Michael Jackson Video Vanguard Award]]), eleven [[BET Awards]], a [[Soul Train Music Award]], and three ''[[Guinness World Records]]''. ''[[Time (magazine)|Time]]'' named her one of the [[Time 100|100 most influential people in the world]] in 2016, and she was honored with the ''Billboard'' Women in Music [[Billboard Women in Music#Game Changer Award|Game Changer Award]] in 2019.
Minaj founded the record label imprint Heavy On It in 2023. Outside of music, her other endeavors include a fragrance line, a [[press on nails]] line, a Loci sneakers collection, and the radio show ''Queen Radio'' (2018–2023). She has also voice acted in the animated films ''[[Ice Age: Continental Drift]]'' (2012) and ''[[The Angry Birds Movie 2]]'' (2019), and acted in the comedy films ''[[The Other Woman (2014 film)|The Other Woman]]'' (2014) and ''[[Barbershop: The Next Cut]]'' (2016). On television, she served as a judge on the [[American Idol season 12|twelfth season]] of ''[[American Idol]]'' (2013). Her outspoken views have received significant media attention.
== Early life ==
Onika Tanya Maraj was born on December 8, 1982,<ref>{{cite magazine |last=Mamo |first=Heran |date=December 8, 2021 |title=Nicki Minaj Celebrates 39th Birthday in Her Birthday Suit: See the Pics |url=https://www.billboard.com/culture/lifestyle/nicki-minaj-birthday-suit-pictures-1235007388/ |url-status=live |archive-url=https://web.archive.org/web/20230628080330/https://www.billboard.com/culture/lifestyle/nicki-minaj-birthday-suit-pictures-1235007388/ |archive-date=June 28, 2023 |access-date=June 28, 2023 |magazine=[[Billboard (magazine)|Billboard]]}}</ref><ref name="IndiaToday">{{cite magazine |date=December 8, 2016 |title=Birthday girl Nicki Minaj has a very interesting Indian connection you didn't know about! |url=https://www.indiatoday.in/lifestyle/music/story/nicki-minaj-india-birthday-hey-mama-music-rapper-lifest-356372-2016-12-08 |url-status=live |archive-url=https://web.archive.org/web/20171224094653/http://indiatoday.intoday.in/story/nicki-minaj-india-birthday-hey-mama-music-rapper-lifest/1/829785.html |archive-date=December 24, 2017 |access-date=August 30, 2017 |magazine=[[India Today]]}}</ref> in the [[Saint James, Trinidad and Tobago|Saint James]] district of [[Port of Spain]], Trinidad and Tobago.<ref name="Biography">{{cite web |title=Nicki Minaj Biography: Rapper (1982–) |url=http://www.biography.com/people/nicki-minaj-579574 |url-status=live |archive-url=https://web.archive.org/web/20151113055603/http://www.biography.com/people/nicki-minaj-579574 |archive-date=November 13, 2015 |access-date=April 22, 2013 |work=[[Biography (TV program)|Biography]]}}</ref><ref name="AM"/> Her father was Robert Maraj (1956–2021),<ref name="father's death">{{cite news |date=February 14, 2021 |title=Nicki Minaj's father, Robert Maraj, killed in hit-and-run accident |url=https://www.theguardian.com/music/2021/feb/14/nicki-minaj-father-robert-maraj-killed-hit-and-run-accident |url-status=live |archive-url=https://web.archive.org/web/20210214202102/https://www.theguardian.com/music/2021/feb/14/nicki-minaj-father-robert-maraj-killed-hit-and-run-accident |archive-date=February 14, 2021 |access-date=February 14, 2021 |newspaper=[[The Guardian]]}}</ref> a financial executive and part-time [[Gospel music|gospel]] singer of [[Dougla]] ([[Afro–Trinidadians and Tobagonians|Afro-Trinidadian]] mother and [[Indo–Trinidadians and Tobagonians|Indo-Trinidadian]] father) descent.<ref name="IndiaToday" /> Her mother, Carol, is also a gospel singer with Afro-Trinidadian ancestry.<ref>{{cite magazine |last=Lazerine |first=Devin |date=April 8, 2014 |title=New Music: Carol Maraj – 'God's Been Good' |url=http://www.rap-up.com/2014/04/08/new-music-carol-maraj-gods-been-good/ |url-status=live |archive-url=https://web.archive.org/web/20230731071900/https://www.rap-up.com/2014/04/08/new-music-carol-maraj-gods-been-good/ |archive-date=July 31, 2023 |access-date=March 27, 2016 |magazine=Rap-Up}}</ref> Her mother worked in payroll and accounting departments during Minaj's youth.<ref>{{cite news |last=Thomasos |first=Christine |date=June 26, 2014 |title=Carol Maraj Exclusive: Nicki Minaj's Mom Says Tithing Helped Rapper, Shares Gospel Music, Abuse |url=http://www.christianpost.com/news/carol-maraj-exclusive-nicki-minajs-mom-says-tithing-helped-rapper-shares-gospel-music-abuse-122292/ |url-status=live |archive-url=https://web.archive.org/web/20230604010227/https://www.christianpost.com/news/carol-maraj-exclusive-nicki-minajs-mom-says-tithing-helped-rapper-shares-gospel-music-abuse-122292/ |archive-date=June 4, 2023 |access-date=March 14, 2016 |newspaper=The Christian Post}}</ref><ref>{{cite news |last=Alexis |first=Nadeska |date=May 9, 2014 |title=Nicki Minaj's Mom, Carol Maraj, Hopes To Inspire Abused Women With Her Music |url=http://www.mtv.com/news/1822809/nicki-minajs-mom-carol-maraj-hopes-to-inspire-abused-women-with-her-music/ |url-status=dead |archive-url=https://web.archive.org/web/20220529120022/http://www.mtv.com/news/1822809/nicki-minajs-mom-carol-maraj-hopes-to-inspire-abused-women-with-her-music/ |archive-date=May 29, 2022 |access-date=March 14, 2016 |publisher=MTV News}}</ref> Her father was an alcoholic and [[crack cocaine]] addict whom Minaj described as violent to her mother and committed [[arson]] on their house in December 1987.<ref>Nicki Minaj, "My Time Again", 2014 MTV Documentary</ref><ref>{{Cite magazine |last=Mier |first=Tomás |date=February 16, 2021 |title=Inside Nicki Minaj's Tumultuous Relationship with Her Late Father Over the Years |url=https://people.com/music/nicki-minaj-relationship-with-late-father-robert-maraj/ |access-date=October 30, 2025 |website=People}}</ref><ref>{{Cite web |last=Rivas |first=Jorge |date=January 28, 2011 |title=Nicki Minaj Gets Personal On Childhood Abuse |url=https://colorlines.com/articles/nicki-minaj-gets-personal-childhood-abuse |access-date=2024-10-05 |work=Colorlines}}</ref><ref name="dailyexpress">{{cite news |title=Carol Maraj, Mother of Hip Hop Star Nicki Minaj |url=http://www.trinidadexpress.com/woman-magazine/Carol_Maraj-163265826.html |url-status=dead |archive-url=https://web.archive.org/web/20151104100803/http://www.trinidadexpress.com/woman-magazine/Carol_Maraj-163265826.html |archive-date=November 4, 2015 |access-date=July 29, 2013 |newspaper=[[Trinidad Express Newspapers]]}}</ref> Minaj has three siblings, including an older brother, a younger brother, and a younger half-sister.<ref>{{Cite news |last=Henderson |first=Cydney |date=January 27, 2020 |title=Nicki Minaj's brother Jelani Maraj sentenced to 25 years to life for raping 11-year-old |url=https://eu.usatoday.com/story/entertainment/celebrities/2020/01/27/nicki-minaj-brother-25-years-life-raping-11-year-old/4592043002/ |url-status=live |archive-url=https://web.archive.org/web/20220516041419/https://eu.usatoday.com/story/entertainment/celebrities/2020/01/27/nicki-minaj-brother-25-years-life-raping-11-year-old/4592043002/ |archive-date=May 16, 2022 |access-date=July 20, 2022 |newspaper=[[USA Today]]}}</ref><ref>{{Cite news |last=Haylock |first=Zoe |date=February 5, 2020 |title=Nicki Minaj Accuses Meek Mill of Physical Abuse During Explosive Twitter Fight |url=https://www.vulture.com/2020/02/nicki-minaj-meek-mill-twitter-fight-2020.html |url-status=live |archive-url=https://web.archive.org/web/20220720105515/https://www.vulture.com/2020/02/nicki-minaj-meek-mill-twitter-fight-2020.html |archive-date=July 20, 2022 |access-date=July 20, 2022 |website=[[Vulture (website)|Vulture]] |quote=Mill also seems to accuse Minaj's younger brother, Micaiah Maraj, of committing sexual abuse.}}</ref><ref>{{Cite news |last=Marie |first=Erika |date=April 25, 2020 |title=Nicki Minaj's Little Sister Ming Maraj Raps Along To 'Moment 4 Life': Report |url=https://www.hotnewhiphop.com/nicki-minajs-little-sister-ming-maraj-raps-along-to-moment-4-life-news.109174.html |url-status=live |archive-url=https://web.archive.org/web/20220720105526/https://www.hotnewhiphop.com/nicki-minajs-little-sister-ming-maraj-raps-along-to-moment-4-life-news.109174.html |archive-date=July 20, 2022 |access-date=July 20, 2022 |work=[[HotNewHipHop]]}}</ref>
As a child, Minaj lived with her grandmother in Saint James in a household with 11 cousins.<ref name="dailyexpress" /> Her mother worked many jobs in Saint James before getting her [[green card]] at the age of 24. She then moved to [[the Bronx]] in New York to attend [[Monroe College]], leaving Minaj in Trinidad with her grandmother. When Minaj was five, Carol brought her to live with her and her father in [[South Jamaica, Queens]].<ref name="dailyexpress" /> Minaj said she arrived in the U.S as an illegal immigrant, after which she gained legal resident status.<ref name="Penrose">{{cite magazine|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-children-border-crisis-illegal-immigrant-8462182/|title=Nicki Minaj Weighs In on Children Border Crisis: 'I Came to This Country as an Illegal Immigrant'|magazine=Billboard|last=Penrose|first=Nerisha|date=June 21, 2018|access-date=May 7, 2021|archive-date=November 10, 2021|archive-url=https://web.archive.org/web/20211110042741/https://www.billboard.com/articles/columns/hip-hop/8462182/nicki-minaj-children-border-crisis-illegal-immigrant|url-status=live}}</ref><ref name="resident"/> She recalled, "I don't think I had a lot of discipline in my household. My mom motivated me, but it wasn't a strict household. I kind of wanted a strict household."<ref>Minaj on ''[[The View (U.S. TV series)|The View]]'' as quoted by {{cite web |author=Scott, Tracy |title=Nicki Minaj advises parents to parent |url=http://s2smagazine.com/20087/nicki-minaj-advises-parents-parent/ |url-status=dead |archive-url=https://web.archive.org/web/20141112072645/http://s2smagazine.com/2012/09/25/nicki-minaj-advises-parents-parent/ |archive-date=November 12, 2014 |access-date=July 29, 2013 |work=[[S2SMagazine.com]] (Interactive One)}}</ref> <!-- She attended PS 45 (the Clarence Witherspoon School) and Elizabeth Blackwell Middle School 210, where she played the clarinet, for her elementary education. -->
Minaj successfully auditioned for admission to [[Fiorello H. LaGuardia High School|Fiorello H. LaGuardia High School of Music & Art and Performing Arts]], which focuses on visual and performing arts.<ref name="Biography" /> A week after graduation, she was cast in the [[Off-Broadway]] play ''In Case You Forget''.<ref>{{cite web |title= Nicki Minaj Gets Emotional As She Reconnects With Her Favorite Teacher|url= https://hiphopdx.com/news/id.74232/title.nicki-minaj-reconnects-former-teacher|date=October 18, 2022 |url-status=live|archive-url=https://web.archive.org/web/20221018222116/https://hiphopdx.com/news/id.74232/title.nicki-minaj-reconnects-former-teacher |archive-date=October 18, 2022|access-date=January 30, 2026|work=HipHopDX}}</ref> In her youth, Minaj worked many jobs, including as a waitress at a [[Red Lobster (restaurant)|Red Lobster]] in the Bronx, customer service representative, and [[office management]] on [[Wall Street]], and was fired for discourtesy to customers.<ref>{{cite web |last=Hope |first=Clover |date=July 15, 2012 |title=Nicki Minaj Does Jay Leno Performance, Discusses Red Lobster Job |url=http://www.vibe.com/article/nicki-minaj-jay-leno-performance-working-red-lobster |url-status=live |archive-url=https://web.archive.org/web/20151105183611/http://www.vibe.com/article/nicki-minaj-jay-leno-performance-working-red-lobster |archive-date=November 5, 2015 |access-date=August 2, 2012 |magazine=[[Vibe (magazine)|Vibe]]}}</ref><ref name="billboard1">{{cite magazine |last=Conception |first=Mariel |date=November 12, 2010 |title=Nicki Minaj: The Billboard Cover Story |url=https://www.billboard.com/articles/news/951113/nicki-minaj-the-billboard-cover-story |url-status=live |archive-url=https://web.archive.org/web/20130528034025/http://www.billboard.com/articles/news/951113/nicki-minaj-the-billboard-cover-story |archive-date=May 28, 2013 |access-date=August 2, 2012 |magazine=Billboard}}</ref><ref>{{cite podcast |url=https://open.spotify.com/episode/2v95xw3U1g65YAnHVTWuwd?si=Ej2Bs1zKR9SVSYhItwrFMg |title=Episode 245: Nicki Minaj & Leslie Jones |website=What's The Tee |host=RuPaul Charles & Michelle Visage |date=March 4, 2020 |time=5:47 |access-date=April 12, 2021 |archive-date=April 12, 2021 |archive-url=https://web.archive.org/web/20210412140202/https://open.spotify.com/episode/2v95xw3U1g65YAnHVTWuwd?si=Ej2Bs1zKR9SVSYhItwrFMg |url-status=live}}</ref>
== Career ==
{{Overly detailed|section|details=the career subsections should be a concise overview of each era, focused on the most significant aspects. Trivia about songs, each album process, and smaller achievements should go instead in the respective album and song articles|nosplit=1|date=December 2025}}
=== 2002–2009: Career beginnings and mixtapes ===
{{Further|Playtime Is Over (mixtape)|Sucka Free|Beam Me Up Scotty (mixtape)}}
From 2002 to 2004, Minaj rapped in a New York hip hop group called Hoodstars, which included rappers Lou$tar and 7even Up and [[hype man]] [[Safaree Samuels]].<ref name="NYP 11">{{cite web|url=https://nypost.com/2011/03/27/darling-nicki/|title=Darling Nicki|work=[[New York Post]]|date=March 27, 2011|access-date=October 12, 2024}}</ref><ref name="Loustar">{{cite web|url=https://www.youtube.com/watch?v=sDt1s8h_8HM|title=Loustar on Forming Hoodstars w/ Nicki Minaj, Safaree Never Writing for Nicki|publisher=VladTV|date=September 18, 2017|access-date=October 11, 2024|via=YouTube|archive-date=September 12, 2024|archive-url=https://web.archive.org/web/20240912161606/https://www.youtube.com/watch?si=WGsH6d6uJefnUs2b&v=sDt1s8h_8HM|url-status=live}}</ref> The son of Bowlegged Lou from [[Full Force]], Lou$tar recruited Minaj when he and his father heard her work.<ref name="Loustar"/><ref name="NYP 11"/> Bowlegged Lou liked Minaj's rapping, and said that back then she was already doing "the crazy voices" in her vocal performances.<ref name="source">{{cite web |url=https://thesource.com/2020/03/13/nicki-minaj-once-shut-down-a-record-deal-after-label-suggested-juelz-santana-as-her-ghostwriter/ |title=Nicki Minaj Once Shut Down a Record Deal After Label Suggested Juelz Santana As Her Ghostwriter |work=[[The Source (magazine) |The Source]] |date=March 13, 2020 |access-date=October 23, 2024 |archive-url=https://web.archive.org/web/20200314151859/https://thesource.com/2020/03/13/nicki-minaj-once-shut-down-a-record-deal-after-label-suggested-juelz-santana-as-her-ghostwriter/ |archive-date=March 14, 2020 |url-status=live |at=In the embedded YouTube video, the topic is discussed at 22:25}}</ref> In 2004, the group recorded the entrance song for [[Women in WWE|WWE Diva]] [[Lisa Marie Varon|Victoria]], "Don't Mess With", which was featured on the compilation album ''[[ThemeAddict: WWE The Music, Vol. 6|ThemeAddict: WWE The Music, Vol.6]]''.<ref>{{cite web | url = http://top40-charts.com/news.php?nid=11223 | title = Columbia Records releases ThemeAddict: WWE The Music V6 in stores now! | date = November 18, 2004 | access-date = September 24, 2016 | archive-date = May 24, 2011 | archive-url = https://web.archive.org/web/20110524184622/http://top40-charts.com/news.php?nid=11223 | url-status = live }}</ref> Full Force tried to get Hoodstars a record deal but were unsuccessful. They then worked with Minaj as a solo artist, meeting with some record companies, including Warner Brothers, who expressed interest in signing Minaj out of the group but required a ghostwriter for a deal. Minaj refused, adamant that she will always write her songs. Brian George from Full Force said that Minaj was impressive in her ambition, describing her as "broke and hungry" while determined to be in control of her craft.<ref name="source"/><ref name="Complex flex"/>
Minaj continued to work independently and uploaded her songs on her [[Myspace]] profile, sending several of them to people in the music industry. In 2006, Fendi, who owned the Brooklyn production company Dirty Money Entertainment, reached out to Minaj on Myspace, and signed her to Dirty Money under a 180-day contract. Originally using the stage name Nicki Maraj, she eventually changed it to Nicki Minaj after Fendi switched her last name when he met her due to her having a "nasty [[flow (rapping)|flow]]".<ref name="Fendi 20">{{cite web|url=https://www.rap-up.com/article/2020/04/16/nicki-minaj-fendi-interview-new-album-label|title=Nicki Minaj Reunites with Former Manager Fendi, Talks New Album & Label|work=[[Rap-Up]]|date=April 16, 2020|access-date=February 4, 2025|archive-url=https://web.archive.org/web/20250204154656/https://www.rap-up.com/article/2020/04/16/nicki-minaj-fendi-interview-new-album-label|archive-date=February 4, 2025|url-status=live}}</ref><ref>{{cite web|url=https://www.hola.com/us/celebrities/20200820fqfjadvrru/your-favorite-celebrities-stage-names/|title=Here's the story behind some of your favorite celebrities' stage names|date=August 20, 2020|access-date=October 26, 2024|archive-url=https://web.archive.org/web/20220129094255/https://www.hola.com/us/celebrities/20200820fqfjadvrru/your-favorite-celebrities-stage-names/|url-status=live|archive-date=January 29, 2022|work=[[¡Hola!]]}}</ref><ref name="Complex flex"/> Minaj wrote a number of songs, including "[[Itty Bitty Piggy]]" and "I Get Crazy", and booked shows in New York and New Jersey.<ref name="Complex flex"/> She released her first mixtape, ''[[Playtime Is Over (mixtape)|Playtime Is Over]]'', on July 5, 2007.<ref>{{cite web|url=http://www.hotnewhiphop.com/nicki-minaj-playtime-is-over-new-mixtape.114353.html|title=Nicki Minaj – Playtime Is Over|last=Rose|first=Lilah|work=HotNewHipHop|date=July 10, 2007|access-date=August 13, 2014|archive-date=August 14, 2014|archive-url=https://web.archive.org/web/20140814011459/http://www.hotnewhiphop.com/nicki-minaj-playtime-is-over-new-mixtape.114353.html|url-status=dead}}</ref> She filmed a music video for the track "Click Clack", which was featured on the underground rap DVD ''The Come Up Vol. 11''.<ref name="spin 10">{{cite web|url=https://www.spin.com/2010/04/breaking-out-nicki-minaj/|title=Breaking Out: Nicki Minaj|work=Spin|date=April 2, 2010|access-date=January 15, 2025|archive-date=January 6, 2025|archive-url=https://web.archive.org/web/20250106020107/https://www.spin.com/2010/04/breaking-out-nicki-minaj/|url-status=live}}</ref> Her rap on the DVD caught the attention of rapper [[Lil Wayne]], who got in contact with Fendi to set up a call with Minaj and asked her to join his crew he was assembling for his new [[imprint label|imprint]] called [[Young Money Entertainment|Young Money]].<ref name="spin 10"/><ref name="Complex flex"/> Minaj collaborated with Wayne, who featured on her next mixtapes, but she did not sign a contract with Young Money then. She continued working on her songs, communicating with fans on social media, and booking shows.<ref name="Complex flex"/><ref name=lipshutz>{{cite magazine|last1=Lipshutz|first1=Jason|title=Nicki Minaj Catches Eyes on Lil Wayne's Young Money Tour|url= https://www.billboard.com/music/music-news/nicki-minaj-catches-eyes-on-lil-waynes-young-money-tour-267795/ |magazine=Billboard|date=August 10, 2009|access-date=February 27, 2015|archive-date=November 30, 2015|archive-url=https://web.archive.org/web/20151130000632/http://www.billboard.com/articles/news/267795/nicki-minaj-catches-eyes-on-lil-waynes-young-money-tour#/news/nicki-minaj-catches-eyes-on-lil-wayne-s-1004001720.story?tag=nextart|url-status=live}}</ref>
Minaj released her second mixtape, ''[[Sucka Free]]'', on April 12, 2008,<ref>{{cite web|url=http://www.livemixtapes.com/mixtapes/2772/nicki_minaj_sucka_free.html|title=Nicki Minaj – Sucka Free (Hosted By Lil Wayne) – Young Money Ent|work=LiveMixtapes|date=April 12, 2008|access-date=August 13, 2014|archive-date=December 6, 2022|archive-url=https://web.archive.org/web/20221206184526/https://www.livemixtapes.com/mixtapes/2772/nicki_minaj_sucka_free.html|url-status=live}}</ref> and her third, ''[[Beam Me Up Scotty (mixtape)|Beam Me Up Scotty]]'', on April 18, 2009;<ref>{{cite web|url=http://www.datpiff.com/Nicki-Minaj-Beam-Me-Up-Scotty-mixtape.42402.html|title=Nicki Minaj – Beam Me Up Scotty Hosted by DJ Holiday & The Trapaholics|work=DatPiff|date=April 18, 2009|access-date=August 13, 2014|archive-date=February 13, 2011|archive-url=https://web.archive.org/web/20110213055130/http://www.datpiff.com/Nicki-Minaj-Beam-Me-Up-Scotty-mixtape.42402.html|url-status=live}}</ref> it received favorable coverage.<ref>{{cite web|url=http://blogs.bet.com/music/sound-off/soundoff-tv-one-on-one-with-lowkey-nicki-minaj/|title=SoundOff TV: One On One With LowKey & Nicki Minaj|work=BET|date=May 1, 2009|access-date=August 13, 2014|archive-date=February 3, 2014|archive-url=https://web.archive.org/web/20140203163857/http://blogs.bet.com/music/sound-off/soundoff-tv-one-on-one-with-lowkey-nicki-minaj/|url-status=dead}}</ref><ref>{{cite web|url=http://www.mtv.com/news/1610492/lil-wayne-introduces-nicki-minaj/|title=Lil Wayne Introduces Nicki Minaj|last=Reid|first=Shaheem|publisher=MTV News|date=May 1, 2009|access-date=August 13, 2014|archive-date=May 24, 2022|archive-url=https://web.archive.org/web/20220524130717/http://www.mtv.com/news/1610492/lil-wayne-introduces-nicki-minaj/|url-status=dead}}</ref><ref name="spin 10"/><ref name="AM"/> The ''Beam Me Up Scotty'' track "[[I Get Crazy]]", reached number 20 on the U.S. ''[[Billboard (magazine)|Billboard]]'' [[Hot Rap Songs]] chart and number 37 on the [[Hot R&B/Hip-Hop Songs]] chart.<ref name="AllMusicAwards">{{cite web|url=https://www.allmusic.com/artist/nicki-minaj-mn0001013175/awards|title=Nicki Minaj: Awards|work=AllMusic|access-date=August 13, 2014|archive-date=October 16, 2014|archive-url=https://web.archive.org/web/20141016002434/http://www.allmusic.com/artist/nicki-minaj-mn0001013175/awards|url-status=live}}</ref> In the summer of 2009, Minaj performed on a tour headlined by Wayne featuring artists from Young Money, among others.<ref name=lipshutz/> At the time she was managed by Debra Antney.<ref name="Complex flex"/> Producer [[Irv Gotti]] asked Minaj to sign to his label [[Murder Inc. Records]] and she countered with an offer for him to manage her, which he declined.<ref name="e733">{{cite web | last=Lelinwalla | first=Mark | title=Nicki Minaj Says Irv Gotti Inspired "Moment 4 Life" Track | website=XXL Mag | date=2012-05-24 | url=https://www.xxlmag.com/nicki-minaj-says-irv-gotti-inspired-moment-4-life-track/ | access-date=2025-03-03}}</ref><ref name="q941">{{cite web | title=Murder Inc. co-founder Irv Gotti turned down the chance to manage Nicki Minaj | website=REVOLT | date=2017-10-03 | url=https://www.revolt.tv/article/2017-10-03/33149/murder-inc-co-founder-irv-gotti-turned-down-the-chance-to-manage-nicki-minaj | access-date=2025-03-03}}</ref> Minaj's mixtapes, [[Freestyle rap|freestyles]], and features on other artists' songs garnered significant industry and public interest.<ref>{{cite magazine|url=https://www.billboard.com/articles/news/959722/nicki-minaj-artists-to-watch-2010|title=Nicki Minaj: Artists To Watch 2010|last=Concepcion|first=Mariel|magazine=Billboard|date=January 15, 2010|access-date=August 13, 2014|archive-date=October 28, 2020|archive-url=https://web.archive.org/web/20201028211619/https://www.billboard.com/articles/news/959722/nicki-minaj-artists-to-watch-2010|url-status=live}}</ref><ref name="rap 09">{{cite web|url=https://www.rap-up.com/article/2009/08/31/nicki-minaj-becomes-young-moneys-first-lady|title= Nicki Minaj Becomes Young Money's First Lady|work=[[Rap-Up]]|date=August 31, 2009|access-date=January 15, 2025|archive-url= https://web.archive.org/web/20241002002129/https://www.rap-up.com/article/2009/08/31/nicki-minaj-becomes-young-moneys-first-lady |archive-date=October 2, 2024|url-status=live}}</ref><ref name="Vibe 09">{{cite web|url= https://www.vibe.com/music/music-news/nicki-minaj-signs-home-team-45836/ |title= Nicki Minaj Signs With The Home Team |work=Vibe|date=August 31, 2009|access-date=January 15, 2025|archive-url= https://web.archive.org/web/20240914025434/https://www.vibe.com/music/music-news/nicki-minaj-signs-home-team-45836/|archive-date=September 14, 2024|url-status=live}}</ref><ref name="xxl 09">{{cite web|url=https://www.xxlmag.com/xxclusive-nicki-minaj-officially-signs-with-young-money/|title=Xxclusive: Nicki Minaj Signs With Young Money/Universal |work=XXL|date=August 31, 2009|access-date=January 15, 2025| archive-url= https://web.archive.org/web/20240623083619/https://www.xxlmag.com/xxclusive-nicki-minaj-officially-signs-with-young-money/ |archive-date=June 23, 2024|url-status=live}}</ref> While she continued playing shows, working on music, and collaborating with other artists, there was a bidding war among record labels to sign her.<ref name="rap 09"/><ref name="Vibe 09"/><ref name="Complex flex">{{cite web|url=https://www.complex.com/music/a/tracewilliamcowen/nicki-minaj-funk-flex-hash-out-differences-80-minute-interview|title=Nicki Minaj and Funk Flex Hash Out Their Differences in 80-Minute Interview|website=Complex|date=August 14, 2018|access-date=October 12, 2024|archive-url=https://web.archive.org/web/20250115232628/https://www.complex.com/music/a/tracewilliamcowen/nicki-minaj-funk-flex-hash-out-differences-80-minute-interview|archive-date=January 15, 2025|url-status=live}}</ref> In late August, Minaj signed a recording contract with Young Money, with her deal entailing her owning her 360 rights, including her merchandising, sponsorships, endorsements, touring and publishing.<ref name="rap 09"/><ref name="xxl 09"/> In a 2011 interview, Minaj said that before she met Lil Wayne, "the person that was spearheading my career was the one person who always told me, 'Don't be too playful, don't be too kooky and weird... no one's gonna feel that, nobody wants to hear that.' So I stifled a lot of that early on, and then once that we parted ways, I was like, 'Guess what, I'm gonna just be me.'"<ref name="IM 21"/> In October 2009, she performed a freestyle at the [[2009 BET Hip Hop Awards]] cypher.<ref>{{cite web|url=https://www.bet.com/photo-gallery/ywcn4s/nicki-minaj-s-road-to-bet-awards-11/13c9d5|title=Nicki Minaj's Road To BET Awards '11|publisher=[[Black Entertainment Television]]|date=2011|access-date=February 4, 2025|archive-url=https://web.archive.org/web/20250204145700/https://www.bet.com/photo-gallery/ywcn4s/nicki-minaj-s-road-to-bet-awards-11/13c9d5|archive-date=February 4, 2025|url-status=live}}</ref> The following month, she appeared with [[Gucci Mane]] and [[Trina]] on the remix of "[[5 Star Bitch]]" by [[Yo Gotti]].<ref>{{cite web|url=https://music.apple.com/us/album/5-star-remix-feat-gucci-mane-trina-nicki-minaj-single/337979349|title=iTunes Music – 5 Star (Remix) [feat. Gucci Mane, Trina & Nicki Minaj] – Single|publisher=iTunes Store|date=November 9, 2009|access-date=August 13, 2014|archive-date=August 26, 2018|archive-url=https://web.archive.org/web/20180826004937/https://itunes.apple.com/us/album/5-star-remix-feat.-gucci-mane/id337979349|url-status=live}}</ref>
In early February 2010, Minaj made her first two appearances on the U.S. [[Billboard Hot 100|''Billboard'' Hot 100]] chart with her features on "[[Knockout (Lil Wayne song)|Knockout]]" and "[[Up Out My Face]]" by [[Lil Wayne]] and [[Mariah Carey]] respectively.<ref>{{Cite magazine|url=https://www.billboard.com/pro/nicki-minaj-first-woman-100-hits-hot-100/|title=Nicki Minaj Makes History as First Woman With 100 Appearances on Billboard Hot 100|access-date=May 26, 2021|magazine=Billboard|archive-date=April 28, 2022|archive-url=https://web.archive.org/web/20220428123608/https://www.billboard.com/pro/nicki-minaj-first-woman-100-hits-hot-100/#:~:text=Minaj%20has%20earned%20all%20100,20%2C%202010.|url-status=live}}</ref><ref name="MCNMUPMFHot 100">{{cite magazine|url=http://www.billboard.com/artist/309388/mariah-carey/chart?f=379|title=Mariah Carey Hot 100 Chart History|magazine=Billboard|date=May 3, 2008|access-date=September 27, 2015|archive-date=January 1, 2015|archive-url=https://web.archive.org/web/20150101001619/http://www.billboard.com/artist/309388/mariah-carey/chart?f=379|url-status=live}}</ref> Minaj also appeared on "[[BedRock]]" and "[[Roger That (song)|Roger That]]" on the [[compilation album]], ''[[We Are Young Money]]'' (2009).<ref>{{cite web |last1=O'Keeffe |first1=Kevin |last2=Reid |first2=Joe |date=August 1, 2014 |title=A Definitive Ranking of Nicki Minaj Guest Verses |website=The Wire |url=http://www.thewire.com/entertainment/2014/08/a-definitive-ranking-of-nicki-minaj-guest-verses/375388/ |access-date=August 13, 2014 |url-status=dead |archive-url=https://web.archive.org/web/20160304064313/http://www.thewire.com/entertainment/2014/08/a-definitive-ranking-of-nicki-minaj-guest-verses/375388/ |archive-date=March 4, 2016}}</ref><ref>{{cite web|url=https://music.apple.com/us/album/we-are-young-money/346046591|title=We Are Young Money by Young Money|publisher=iTunes Store|date=December 21, 2009|access-date=August 13, 2014|archive-date=August 25, 2018|archive-url=https://web.archive.org/web/20180825212607/https://itunes.apple.com/us/album/we-are-young-money/id346046591|url-status=live}}</ref> The singles peaked at numbers two and 56, respectively, in the U.S. Their parent album reached number nine on the U.S. [[Billboard 200|''Billboard'' 200]], and was certified [[RIAA certification|platinum]] by the [[Recording Industry Association of America]] (RIAA).<ref name="YoungMoneyAwards">{{cite web |title=Young Money: Awards |url=https://www.allmusic.com/artist/young-money-mn0001391253/awards |access-date=August 13, 2014 |work=AllMusic |archive-date=March 5, 2024 |archive-url=https://web.archive.org/web/20240305180453/https://www.allmusic.com/artist/young-money-mn0001391253#awards |url-status=live }}</ref>{{Certification Cite Ref|region=United States|type=album|title=We Are Young Money|artist=Young Money|access-date=August 13, 2014}} At [[Jay-Z]]'s suggestion, [[Robin Thicke]] featured Minaj on his single "[[Shakin' It 4 Daddy]]".<ref>{{cite web|url=http://www.mtv.com/news/1631352/nicki-minaj-gets-stamp-of-approval-from-jay-z/|title=Nicki Minaj Gets 'Stamp Of Approval' From Jay-Z|last=Reid|first=Shaheem|publisher=MTV News|date=February 5, 2010|access-date=August 13, 2014|archive-date=May 28, 2022|archive-url=https://web.archive.org/web/20220528093948/http://www.mtv.com/news/1631352/nicki-minaj-gets-stamp-of-approval-from-jay-z/|url-status=dead}}</ref> Allison Stewart of ''[[The Washington Post]]'' stated that, during that time, she "became the go-to girl for artists who wanted to add some skank to their tracks without sullying themselves in the process".<ref>{{cite news|last=Stewart|first=Allison|url=https://www.washingtonpost.com/wp-dyn/content/article/2010/01/11/AR2010011103195.html|title=Reviews of singles from Justin Vernon, Mariah Carey, Maino, Quasi and Tony Yayo|newspaper=[[The Washington Post]]|date=January 12, 2010|access-date=September 27, 2015|archive-date=September 25, 2015|archive-url=https://web.archive.org/web/20150925134639/http://www.washingtonpost.com/wp-dyn/content/article/2010/01/11/AR2010011103195.html|url-status=live}}</ref> Minaj became the first female solo artist to have seven singles simultaneously charting in the U.S.<ref>{{Cite news|title=Nicki Minaj Makes History With Seven Billboard Hot 100 Songs|url=https://www.mtv.com/news/2494382/nicki-minaj-makes-history-with-seven-billboard-hot-100-songs/|access-date=August 25, 2021|publisher=MTV News|archive-date=August 25, 2021|archive-url=https://web.archive.org/web/20210825143130/https://www.mtv.com/news/2494382/nicki-minaj-makes-history-with-seven-billboard-hot-100-songs/|url-status=dead}}</ref>
=== 2010–2011: Breakthrough with ''Pink Friday'' ===
{{Further|Pink Friday}}[[File:MinajHammersteinBallroomNYC.jpg|thumb|Minaj performing in 2010|upright]]
On March 29, 2010, Minaj released "[[Massive Attack (song)|Massive Attack]]" featuring [[Sean Garrett]].<ref>{{cite web|url=http://www.rap-up.com/2010/03/29/new-music-nicki-minaj-f-sean-garrett-massive-attack/|title=New Music: Nicki Minaj f/ Sean Garrett – 'Massive Attack'|website=Rap-Up|publisher=Rap-Up, LLC|date=March 29, 2010|access-date=August 13, 2014|archive-date=March 22, 2016|archive-url=https://web.archive.org/web/20160322025133/http://www.rap-up.com/2010/03/29/new-music-nicki-minaj-f-sean-garrett-massive-attack/|url-status=live}}</ref> Intended as the lead single from her forthcoming debut album, ''[[Pink Friday]]'', the song was dropped from the album due to poor commercial performance.<ref>{{cite web |date=August 3, 2010 |title=Nicki Minaj Announces Album Title |url=http://www.rap-up.com/2010/08/03/nicki-minaj-announces-album-title/ |access-date=August 13, 2014 |website=Rap-Up |publisher=Rap-Up, LLC |archive-date=March 31, 2016 |archive-url=https://web.archive.org/web/20160331073718/http://www.rap-up.com/2010/08/03/nicki-minaj-announces-album-title/ |url-status=live }}</ref><ref>{{cite magazine |last=Concepcion |first=Mariel |date=October 20, 2010 |title=Sean Garrett: Nicki Minaj's Failed Single Was 'Out Of My Control' |url=https://www.billboard.com/music/music-news/sean-garrett-nicki-minajs-failed-single-was-out-of-my-control-953218/ |magazine=Billboard |access-date=August 13, 2014 |archive-date=September 7, 2014 |archive-url=https://web.archive.org/web/20140907092701/http://www.billboard.com/articles/columns/the-juice/953218/sean-garrett-nicki-minajs-failed-single-was-out-of-my-control |url-status=live }}</ref> The next single, "[[Your Love (Nicki Minaj song)|Your Love]]", later became the album's lead single.<ref>{{cite web|url=http://www.rap-up.com/2010/05/20/single-cover-nicki-minaj-your-love/|title=Single Cover: Nicki Minaj – 'Your Love'|website=Rap-Up|publisher=Rap-Up, LLC|date=May 20, 2010|access-date=August 13, 2014|archive-date=August 18, 2016|archive-url=https://web.archive.org/web/20160818020205/http://www.rap-up.com/2010/05/20/single-cover-nicki-minaj-your-love/|url-status=live}}</ref> Released in June, it peaked at number 14 on the U.S. ''Billboard'' Hot 100 and number one on the Rap Songs chart.<ref name="AllMusicAwards" /> The rapper was then featured on [[Christina Aguilera]]'s song "[[Woohoo (Christina Aguilera song)|Woohoo]]", which reached number 46 on the [[Canadian Hot 100]] and number 79 on ''Billboard'' Hot 100.<ref>{{cite web|url=http://buzzworthy.mtv.com/2010/05/10/song-you-need-to-know-christina-aguilera-featuring-nicki-minaj-woohoo/|title=Song You Need To Know: Christina Aguilera, Featuring Nicki Minaj, 'Woohoo'|access-date=June 10, 2024|date=May 18, 2010|first=Chris|last=Ryan|publisher=MTV|archive-url=https://web.archive.org/web/20100514150852/http://buzzworthy.mtv.com/2010/05/10/song-you-need-to-know-christina-aguilera-featuring-nicki-minaj-woohoo/|archive-date=May 14, 2010}}</ref><ref>{{cite magazine|url=https://www.billboard.com/artist/christina-aguilera/chart-history/can/|title=Christina Aguilera Chart History (Canadian Hot 100)|magazine=[[Billboard (magazine)|Billboard]]|access-date=June 10, 2024|archive-date=November 17, 2021|archive-url=https://web.archive.org/web/20211117175038/https://www.billboard.com/artist/christina-aguilera/chart-history/can/|url-status=live}}</ref><ref>{{cite magazine|url=https://www.billboard.com/artist/christina-aguilera/chart-history/hsi/|title=Christina Aguilera Chart History (Hot 100)|magazine=[[Billboard (magazine)|Billboard]]|access-date=June 10, 2024|archive-date=November 21, 2021|archive-url=https://web.archive.org/web/20211121194955/https://www.billboard.com/artist/christina-aguilera/chart-history/hsi/|url-status=live}}</ref> In September, Minaj released "[[Check It Out (will.i.am and Nicki Minaj song)|Check It Out]]" with [[will.i.am]] and "[[Right Thru Me]]" as follow-up singles.<ref>{{cite web|url=https://music.apple.com/us/album/check-it-out-single/1445290325|title=iTunes Music – Check It Out – Single by will.i.am & Nicki Minaj|work=iTunes Store (US)|publisher=Apple Inc.|date=September 3, 2010|access-date=August 13, 2014|archive-date=September 6, 2011|archive-url=https://web.archive.org/web/20110906065748/http://itunes.apple.com/us/album/check-it-out-single/id391025074|url-status=live}}</ref><ref>{{cite web|url=http://www.idolator.com/5634981/nicki-minaj-right-through-me-asks-how-do-you-do-that-sht|title=Nicki Minaj's 'Right Thru Me' Asks, 'How Do You Do That Sh*t?'|last=Brooks Adickman|first=Erika|work=Idolator|publisher=Spin Media|date=September 22, 2010|access-date=August 13, 2014|archive-date=March 5, 2024|archive-url=https://web.archive.org/web/20240305190511/https://www.idolator.com/5634981/nicki-minaj-right-through-me-asks-how-do-you-do-that-sht|url-status=dead}}</ref><ref>{{Cite magazine|url=https://www.billboard.com/music/rb-hip-hop/kanye-west-monster-nicki-minaj-instagram-seventh-anniversary-8013986/|title=Nicki Minaj Says Kanye West's 'Monster' Almost Didn't Make His 'MBDTF' Album|magazine=Billboard|access-date=April 30, 2019|archive-date=May 2, 2019|archive-url=https://web.archive.org/web/20190502232857/https://www.billboard.com/articles/columns/hip-hop/8013986/kanye-west-monster-nicki-minaj-instagram-seventh-anniversary|url-status=live}}</ref>
In October, Minaj was featured on [[Kanye West]]'s "[[Monster (Kanye West song)|Monster]]", a [[posse cut]] with [[Jay-Z]] and [[Rick Ross]] featuring vocals from [[Justin Vernon]] of [[Bon Iver]]. Her verse received widespread acclaim with many critics regarding it as the best verse on that song. [[Complex (magazine)|''Complex'']] rated Minaj's "Monster" verse as the number-one best rap verse in the past five years,<ref>{{Cite magazine|title=The 25 Best Rap Verses of the Last 5 Years|url=https://www.complex.com/music/2013/05/best-rap-verses-of-the-last-5-years/|access-date=August 19, 2021|magazine=Complex|date=May 29, 2013 |archive-date=September 16, 2021|archive-url=https://web.archive.org/web/20210916145516/https://www.complex.com/music/2013/05/best-rap-verses-of-the-last-5-years/|url-status=live}}</ref> while Sean Fennessey of ''[[The Village Voice]]'' stated that "Monster" was "the track that announced Minaj's "brilliance" to most people."<ref name="Fennessey">Fennessey, Sean (November 17, 2010). [https://web.archive.org/web/20101120015711/http://www.villagevoice.com/2010-11-17/music/kanye-west-finally-unraveled/ Review: ''My Beautiful Dark Twisted Fantasy''] . ''[[The Village Voice]]''. Retrieved August 10, 2020.</ref> Shortly after, she performed "Monster" with West and Jay-Z at [[Yankee Stadium]], becoming the first ever female rapper to perform there. In November 2010, Minaj received her first [[Grammy Award]] nomination for her guest verse on [[Ludacris]]' song "[[My Chick Bad]]".<ref>{{Cite news|url=https://www.grammy.com/artists/nicki-minaj/4579|title=Nicki Minaj|date=November 28, 2017|publisher=Grammy Awards|access-date=February 22, 2018|archive-date=June 27, 2020|archive-url=https://web.archive.org/web/20200627034621/https://www.grammy.com/grammys/artists/nicki-minaj|url-status=live}}</ref>
''Pink Friday'' was released on November 19, 2010, debuting at number two and later reaching number one on the ''Billboard'' 200, with first-week sales of 375,000 copies.<ref>{{cite web|date=November 19, 2010|title=Pink Friday|url=https://www.amazon.de/dp/B0042RUMEQ/|access-date=August 13, 2014|website=Amazon Germany|archive-date=November 5, 2015|archive-url=https://web.archive.org/web/20151105183746/http://www.amazon.de/dp/B0042RUMEQ/|url-status=live}}</ref><ref name="OpeningWeeks">{{cite magazine|url=https://www.billboard.com/articles/news/496716/nicki-minajs-roman-reloaded-debuts-at-no-1-on-billboard-200|title=Nicki Minaj's 'Roman Reloaded' Debuts at No. 1 on Billboard 200|last=Caulfield|first=Keith|magazine=Billboard|date=April 11, 2012|access-date=August 13, 2014|archive-date=April 17, 2021|archive-url=https://web.archive.org/web/20210417020406/https://www.billboard.com/articles/news/496716/nicki-minajs-roman-reloaded-debuts-at-no-1-on-billboard-200|url-status=live}}</ref> It had the largest sales week for a female rap album in the 21st century and second-highest sales week overall after [[Lauryn Hill]]'s ''[[The Miseducation of Lauryn Hill]]'' (1998). The album also became the female rap album with the most weeks in the top ten on the ''Billboard'' 200 chart at the time, with fourteen consecutive weeks in the region.<ref name="PFtop10">{{cite web |date=March 3, 2011 |title=Nicki Minaj Breaks ''Billboard'' Female Rap Album Record |url=http://rapfix.mtv.com/2011/03/03/nicki-minaj-breaks-billboard-female-rap-album-record/ |url-status=dead |archive-url=https://web.archive.org/web/20110306003321/http://rapfix.mtv.com/2011/03/03/nicki-minaj-breaks-billboard-female-rap-album-record/ |archive-date=March 6, 2011 |access-date=September 15, 2024 |publisher=[[MTV]]}}</ref> Upon release, the album received generally positive reviews from critics.<ref name="mc">{{cite web|title=Pink Friday|url=https://www.metacritic.com/music/pink-friday|access-date=November 24, 2010|publisher=[[Metacritic]]|archive-date=March 23, 2021|archive-url=https://web.archive.org/web/20210323183113/https://www.metacritic.com/music/pink-friday|url-status=live}}</ref> The song "[[Roman's Revenge]]", featuring [[Eminem]], was interpreted as a response to rapper [[Lil' Kim]]'s comments against Minaj,<ref>{{cite magazine|title=Is Nicki Minaj attacking Lil' Kim on 'Roman's Revenge'?|url=https://www.billboard.com/music/music-news/is-nicki-minaj-attacking-lil-kim-on-romans-revenge-track-952092/|date=November 1, 2010|magazine=Billboard|access-date=August 21, 2012|archive-date=January 7, 2022|archive-url=https://web.archive.org/web/20220107212553/https://www.billboard.com/music/music-news/is-nicki-minaj-attacking-lil-kim-on-romans-revenge-track-952092/|url-status=live}}</ref> including her accusing Minaj of copying her image and that she had recorded a song called "Everywhere We Go" with Minaj but it was pulled from [[iTunes]] by [[Cash Money Records|Cash Money]] because it did not perform well.<ref name="NYDailyNews">{{cite news|title=Lil' Kim 'Black Friday' mixtape cover decapitates Nicki Minaj; rapper's old rival laughs off diss|url=http://articles.nydailynews.com/2011-02-16/entertainment/28622261_1_nicki-minaj-roman-s-revenge-radio-station|newspaper=New York Daily News |access-date=August 21, 2012|date=February 16, 2011|archive-date=October 25, 2012|archive-url=https://web.archive.org/web/20121025200016/http://articles.nydailynews.com/2011-02-16/entertainment/28622261_1_nicki-minaj-roman-s-revenge-radio-station|url-status=dead}}</ref> At the time, Minaj said that "Roman's Revenge" was not about a specific person, but about "everyone who has been in interviews talking", adding, "I've been quiet for such a long time and I've been the nice guy. I've been the person that gave everyone their props ... you can't be a rapper and not speak on things being said about you".<ref>{{cite magazine|title=Nicki Minaj says 'Roman's Revenge's is not about Lil' Kim|url=https://www.billboard.com/music/music-news/nicki-minaj-says-romans-revenge-is-not-about-lil-kim-951771/|magazine=Billboard|date=November 4, 2010|access-date=August 21, 2012|archive-date=October 1, 2021|archive-url=https://web.archive.org/web/20211001180011/https://www.billboard.com/articles/columns/the-juice/951771/nicki-minaj-says-romans-revenge-is-not-about-lil-kim|url-status=live}}</ref> In a 2018 interview, she explained that she wrote the song after "a veteran got at me, and I hit them with 'Roman's Revenge' and then I kept on going."<ref>{{Cite web|title=Nicki Minaj Reveals The Real Reason Behind Her Lil' Kim Diss On 'Roman's Revenge'|url=https://www.capitalxtra.com/artists/nicki-minaj/news/real-reason-behind-lil-kim-diss-romans-revenge/|date=August 16, 2018|access-date=July 9, 2021|publisher=[[Capital Xtra]]|archive-date=April 19, 2021|archive-url=https://web.archive.org/web/20210419051546/https://www.capitalxtra.com/artists/nicki-minaj/news/real-reason-behind-lil-kim-diss-romans-revenge/|url-status=live}}</ref>
[[File:Nicki Minaj Femme 2011.jpg|thumb|231x231px|Minaj in 2011]]
''Pink Friday'' was certified platinum by the [[Recording Industry Association of America]] (RIAA) in December 2010, for accumulating a million units in the United States. It became the first album by a solo female rapper to go platinum in seven years.<ref>{{cite web |date=January 5, 2011 |title=Nicki Minaj's 'Pink Friday' Certified Platinum |url=http://www.rap-up.com/2011/01/05/nicki-minajs-pink-friday-certified-platinum/ |access-date=August 13, 2014 |website=Rap-Up |publisher=Rap-Up, LLC |archive-date=July 31, 2023 |archive-url=https://web.archive.org/web/20230731071902/https://www.rap-up.com/2011/01/05/nicki-minajs-pink-friday-certified-platinum/ |url-status=live}}</ref><ref>{{Cite magazine|last=Conteh|first=Mankaprr|date=May 19, 2021|title=Nicki Minaj Revisits a Distant Golden Age With 'Beam Me Up Scotty'|url=https://www.rollingstone.com/music/music-features/nicki-minaj-beam-me-up-scotty-re-release-1171681/|access-date=May 20, 2021|magazine=Rolling Stone|archive-date=May 20, 2021|archive-url=https://web.archive.org/web/20210520091356/https://www.rollingstone.com/music/music-features/nicki-minaj-beam-me-up-scotty-re-release-1171681/|url-status=live}}</ref> As of March 2016, it has been certified triple-platinum.<ref>{{cite certification|region=United States|artist=Nicki Minaj|title=Pink Friday|type=album|access-date=September 15, 2024}}</ref> "[[Moment 4 Life]]" featuring [[Drake (musician)|Drake]] was released as the fourth single from ''Pink Friday'',<ref>{{cite web|url=http://www.fmqb.com/Article.asp?id=69239#210 |title=FMQB: Radio Industry News, Music Industry Updates, Nielsen Ratings, Music News and more! |website=FMQB |access-date=August 13, 2014 |url-status=dead |archive-url=https://web.archive.org/web/20110705122717/http://www.fmqb.com/Article.asp?id=69239 |archive-date=July 5, 2011}}</ref> on December 7, 2010. It reached number 13 on the Hot 100.<ref name="20biggesthits">{{Cite magazine |last=Dailey |first=Hannah |date=February 26, 2024 |title=Nicki Minaj's 20 Biggest ''Billboard'' Hits, From 'Super Bass' to 'Super Freaky Girl' |url=https://www.billboard.com/lists/nicki-minaj-top-songs-billboard-hot-100/only-featuring-drake-lil-wayne-chris-brown/ |access-date=September 15, 2024 |magazine=Billboard |archive-date=September 15, 2024 |archive-url=https://web.archive.org/web/20240915134804/https://www.billboard.com/lists/nicki-minaj-top-songs-billboard-hot-100/only-featuring-drake-lil-wayne-chris-brown/ |url-status=live}}</ref> Minaj performed "Right Thru Me" and "Moment 4 Life" as the musical guest on the [[Saturday Night Live season 36#ep693|January 29, 2011, episode]] of ''[[Saturday Night Live]]''.<ref>{{cite web|url=https://www.spin.com/2011/01/nicki-minaj-dominates-saturday-night-live/|title=Nicki Minaj Dominates 'Saturday Night Live'|last=O'Donnell|first=Kevin|magazine=[[Spin (magazine)|Spin]]|date=January 30, 2011|access-date=August 13, 2014|archive-date=November 25, 2015|archive-url=https://web.archive.org/web/20151125235013/http://www.spin.com/2011/01/nicki-minaj-dominates-saturday-night-live/|url-status=live}}</ref> In February 2011, Lil' Kim released the mixtape ''Black Friday'' referencing Minaj, including artwork that showed Lil' Kim decapitating Minaj with a sword and the lyrics "sweetie, you goin' on your 14th minute of fame" and "you a Lil' Kim wannabe". Lil' Kim sold the mixtape on [[PayPal]].<ref name="NYDailyNews" /> Minaj's track "Tragedy" was released online, featuring the lyrics "Pink Friday, Eminem, 8 Mile/ It must hurt to sell your album on PayPal."<ref>{{cite web|title=Music Nicki Minaj aims new diss at "tragic" Lil' Kim|url=https://www.digitalspy.com/music/a312696/nicki-minaj-aims-new-diss-at-tragic-lil-kim/|publisher=Digital Spy|date=April 4, 2011|access-date=August 21, 2012|archive-date=July 11, 2019|archive-url=https://web.archive.org/web/20190711113030/https://www.digitalspy.com/music/a312696/nicki-minaj-aims-new-diss-at-tragic-lil-kim/|url-status=live}}</ref>
"[[Super Bass]]" was released as ''Pink Friday''{{'}}s fifth single in April 2011.<ref>{{cite web|url=https://itunes.apple.com/au/album/super-bass-single/id440437982|title=iTunes Music – Super Bass – Single by Nicki Minaj|work=iTunes Store (AU)|publisher=Apple Inc.|date=May 13, 2011|access-date=August 13, 2014|archive-date=June 2, 2011|archive-url=https://web.archive.org/web/20110602100429/http://itunes.apple.com/au/album/super-bass-single/id440437982|url-status=dead}}</ref> It became a [[sleeper hit]] and commercially successful, ultimately peaking at number three on the Hot 100.<ref name="AllMusicAwards" /> It was eventually certified 12-times platinum in the US, for selling 12 million units;<ref>{{Cite web |title=American single certifications – Nicki Minaj – Super Bass |url=https://www.riaa.com/gold-platinum/?tab_active=default-award&ar=Nicki+Minaj&ti=Super+Bass&format=Single&type=#search_section |access-date=March 31, 2024 |publisher=[[Recording Industry Association of America]] |archive-date=July 13, 2023 |archive-url=https://web.archive.org/web/20230713044524/https://www.riaa.com/gold-platinum/?tab_active=default-award&ar=Nicki+Minaj&ti=Super+Bass&format=Single&type=#search_section |url-status=live}}</ref> it became [[List of best-selling female rappers|the highest-certified song by a female rapper]] in RIAA history.<ref name=":5" /> "Super Bass" was the highest-charting solo single by a female rapper, at the time, since [[Missy Elliott]]'s "[[Work It (Missy Elliott song)|Work It]]" in 2002. In 2017, ''Billboard'' ranked it as the second-biggest US Hot 100 hit by a female rapper of all time (behind [[Iggy Azalea]]'s "Fancy");<ref>{{Cite magazine |last=Zellner |first=Xander |date=September 28, 2017 |title=Missy, Nicki, Cardi & More: The Top 25 Biggest Hot 100 Hits by Female Rappers |url=https://www.billboard.com/pro/female-rappers-biggest-hits-hot-100-cardi-b-iggy-azalea/ |url-access=subscription |access-date=September 15, 2024 |magazine=Billboard |archive-date=March 23, 2024 |archive-url=https://web.archive.org/web/20240323180446/https://www.billboard.com/pro/female-rappers-biggest-hits-hot-100-cardi-b-iggy-azalea/ |url-status=live}}</ref> and in 2024, ranked it as the biggest Hot 100 hit of Minaj's career.<ref name="20biggesthits"/> Its music video has over 1 billion views on YouTube, as of October 2024.<ref>{{cite web |via=YouTube |title=Nicki Minaj – Super Bass| date=May 5, 2011 |url=https://www.youtube.com/watch/4JipHEz53sU|access-date=September 15, 2024|archive-date=August 13, 2021|archive-url=https://web.archive.org/web/20210813082831/https://www.youtube.com/watch?v=4JipHEz53sU|url-status=live}}</ref><ref>{{Cite tweet|title=.@NickiMinaj's "Super Bass" has now surpassed 900 MILLION views on YouTube!!! It's her 9th music video to achieve this. Congrats Nicki!|user=New1027|number=1425917505411985411|access-date=August 13, 2021||archive-date=August 13, 2021|archive-url=https://web.archive.org/web/20210813102157/https://twitter.com/new1027/status/1425917505411985411|url-status=live}}</ref>{{Primary source inline|date=July 2024}}
Minaj was one of the opening acts on [[Britney Spears]]' 2011 [[Femme Fatale Tour]].<ref>{{cite web|url=http://www.mtv.com/news/1661786/britney-spears-nicki-minaj-femme-fatale-tour/|title=Britney Spears To Tour With Nicki Minaj|last=Vena|first=Jocelyn|publisher=MTV News|date=April 12, 2011|access-date=August 13, 2014|archive-date=May 27, 2022|archive-url=https://web.archive.org/web/20220527041741/http://www.mtv.com/news/1661786/britney-spears-nicki-minaj-femme-fatale-tour/|url-status=dead}}</ref> She and [[Kesha]] appeared on the remix of Spears' "[[Till the World Ends]]", which peaked at number three in the U.S.<ref>{{cite web|url=https://www.allmusic.com/artist/britney-spears-mn0000628467/awards|title=Britney Spears: Awards|publisher=AllMusic|access-date=August 14, 2014|archive-date=June 16, 2013|archive-url=https://web.archive.org/web/20130616010617/http://www.allmusic.com/artist/britney-spears-mn0000628467/awards|url-status=live}}</ref> On August 7, 2011, Minaj experienced a [[wardrobe malfunction]] during a live performance on ''[[Good Morning America]]'' when she revealed her left nipple. Both [[American Broadcasting Company|ABC]] and Minaj apologized for the incident, with Minaj denying it was a publicity stunt.<ref>[https://web.archive.org/web/20120616112055/http://www.vibe.com/article/10-most-controversial-moments-nicki-minajs-career-pg-8 "The 10 Most Controversial Moments of Nicki Minaj's Career, Pg. 8."] ''Vibe''. June 5, 2012.</ref><ref>Johnson, Zach. [http://www.usmagazine.com/celebrity-news/news/nicki-minaj-addresses-good-morning-america-nip-slip-it-wasnt-a-stunt-2012104 "Nicki Minaj addresses Good Morning America nip slip, 'it wasn't a stunt.'"] {{Webarchive|url=https://web.archive.org/web/20230731071859/https://www.usmagazine.com/celebrity-news/news/nicki-minaj-addresses-good-morning-america-nip-slip-it-wasnt-a-stunt-2012104/ |date=July 31, 2023 }} ''US Magazine''. April 10, 2012.</ref> Minaj continued to perform at high-profile events throughout 2011 with [[Donatella Versace]] inviting her to perform with [[Prince (musician)|Prince]] for the introduction of a [[Versace]] collection for [[H&M]]. She also performed "Super Bass" at the 2011 [[Victoria's Secret Fashion Show]].<ref>{{cite magazine|last=Nika|first=Colleen|title=Nicki Minaj Wows Fashion World With Versace for H&M and Victoria's Secret Gigs|url=https://www.rollingstone.com/music/blogs/thread-count/nicki-minaj-wows-fashion-world-with-versace-for-h-m-and-victorias-secret-gigs-20111111|archive-url=https://web.archive.org/web/20111112152410/http://www.rollingstone.com/music/blogs/thread-count/nicki-minaj-wows-fashion-world-with-versace-for-h-m-and-victorias-secret-gigs-20111111|url-status=dead|archive-date=November 12, 2011|magazine=Rolling Stone|access-date=August 19, 2012}}</ref> In December 2011, Minaj was nominated for three Grammy Awards, including [[Grammy Award for Best New Artist|Best New Artist]] and [[Grammy Award for Best Rap Album|Best Rap Album]] for ''Pink Friday''.<ref>{{Cite news|url=http://www.mtv.com/news/2496524/nicki-minaj-j-cole-nominated-for-best-new-artist-grammy/|title=Nicki Minaj, J. Cole Nominated For Best New Artist Grammy|publisher=MTV News|access-date=February 22, 2018|language=en|archive-date=May 25, 2022|archive-url=https://web.archive.org/web/20220525053759/http://www.mtv.com/news/2496524/nicki-minaj-j-cole-nominated-for-best-new-artist-grammy/|url-status=dead}}</ref> Also that year, she won the [[MTV Video Music Award for Best Hip-Hop Video]] for "[[Super Bass]]", marking her first [[MTV Video Music Award|VMA]] win.<ref>{{Cite news|url=http://www.mtv.com/news/1669845/nicki-minaj-vma-best-hip-hop-video-winner/|title=Nicki Minaj Wins Best Hip-Hop Video VMA|publisher=MTV News|access-date=August 23, 2018|language=en|archive-date=June 8, 2022|archive-url=https://web.archive.org/web/20220608145201/http://www.mtv.com/news/1669845/nicki-minaj-vma-best-hip-hop-video-winner/|url-status=dead}}</ref> In 2022, the album was included in ''[[Rolling Stone]]''{{'}}s list of "200 Greatest Hip-Hop Albums of All Time" at number 31; the magazine stated that ''Pink Friday'' "proved you could own the charts without dialing back your confrontational individuality, and it set the table for a generation of artists."<ref>{{Cite magazine |first1=Charles |last1=Aaron |first2=Mankaprr |last2=Conteh |first3=Jon |last3=Dolan |first4=Will |last4=Dukes |first5=Dewayne |last5=Gage |first6=Joe |last6=Gross |first7=Kory |last7=Grow |first8=Christian |last8=Hoard |first9=Jeff |last9=Ihaza |first10=Julyssa |last10=Lopez |first11=Mosi |last11=Reeves |first12=Yoh |last12=Phillips |first13=Noah |last13=Shachtman |first14=Rob |last14=Sheffield |first15=Simon |last15=Vozick-Levinson |first16=Christopher |last16=R. |date=June 7, 2022 |title=The 200 Greatest Hip-Hop Albums of All Time |url=https://www.rollingstone.com/music/music-lists/best-hip-hop-albums-1323916/nicki-minaj-pink-friday-roman-reloaded-2012-1353876/ |access-date=September 15, 2024 |magazine=Rolling Stone |archive-date=September 12, 2024 |archive-url=https://web.archive.org/web/20240912065811/https://www.rollingstone.com/music/music-lists/best-hip-hop-albums-1323916/nicki-minaj-pink-friday-roman-reloaded-2012-1353876/ |url-status=live}}</ref> In 2024, ''[[Vibe (magazine)|Vibe]]'' named it as one of the "50 Greatest Black Albums of the Modern Era".<ref>{{Cite magazine |last=Brown |first=Preezy |date=October 1, 2024 |title=50 Greatest Black Albums Of The Modern Era |url=https://www.vibe.com/lists/greatest-black-albums/pink-friday-nicki-minaj-2010/ |access-date=October 9, 2024 |magazine=[[Vibe (magazine)|Vibe]]}}</ref>
=== 2012–2013: ''Pink Friday: Roman Reloaded'' and ''American Idol'' ===
{{Further|Pink Friday: Roman Reloaded|Pink Friday: Roman Reloaded – The Re-Up}}[[File:Nicki Minaj Hammersmith Apollo 2012 Nicki Minaj Hammersmith Apollo 2012 IMG 4388 (7449009258) (cropped).jpg|thumb|Minaj performing in the [[Hammersmith Apollo]] during 2012|upright]]
"[[Starships (song)|Starships]]" was released in February 2012 as the lead single from Minaj's forthcoming second album, ''[[Pink Friday: Roman Reloaded]]''.<ref>{{cite web|url=http://ryanseacrest.com/2012/02/14/world-premiere-listen-to-nicki-minajs-new-single-starships-audio/|title=World Premiere: Listen To Nicki Minaj's New Single 'Starships'|last=Murray|first=Michael|date=February 14, 2014|website=On Air with Ryan Seacrest|archive-url=https://web.archive.org/web/20120216094300/http://ryanseacrest.com/2012/02/14/world-premiere-listen-to-nicki-minajs-new-single-starships-audio/|archive-date=February 16, 2012|access-date=August 14, 2014}}</ref> The song reached number five on the US ''Billboard'' Hot 100,<ref name="AllMusicAwards" /> was the fifth best-selling single of 2012,<ref>{{cite web|url=http://www.nation.com.pk/entertainment/03-Dec-2012/gotye-has-best-selling-single-of-2012|title=Gotye has best-selling single of 2012|date=December 3, 2012|website=The Nation|publisher=Katrina vanden Heuvel|access-date=August 14, 2014|archive-date=November 29, 2014|archive-url=https://web.archive.org/web/20141129114335/http://nation.com.pk/entertainment/03-Dec-2012/gotye-has-best-selling-single-of-2012|url-status=live}}</ref> and was certified diamond by the RIAA in November 2024, for moving over ten million units in the US.<ref name="starshipdiamond">{{Cite magazine |last=Mendez |first=Marisa |date=November 19, 2024 |title=Nicki Minaj's 'Starships' Garners This Major Accolade After A Dozen Years |url=https://hiphopdx.com/news/nicki-minaj-starships-major-accolade/ |access-date=November 19, 2024 |magazine=[[HipHopDX]] |archive-date=November 19, 2024 |archive-url=https://web.archive.org/web/20241119181642/https://hiphopdx.com/news/nicki-minaj-starships-major-accolade |url-status=live }}</ref> Her crossover into pop music was criticized by some, despite her commercial success.<ref name="crossover">{{cite web|url=http://www.mtv.com/news/1682589/nicki-minaj-roman-reloaded/|title=Nicki Minaj Looking For Ultimate Hip-Hop/Pop Crossover|author=Kaufman, Gil|date=April 5, 2012|publisher=MTV News|access-date=December 9, 2015|archive-date=May 24, 2022|archive-url=https://web.archive.org/web/20220524193306/http://www.mtv.com/news/1682589/nicki-minaj-roman-reloaded/|url-status=dead}}</ref> Follow-up singles "[[Beez in the Trap]]" featuring [[2 Chainz]] and "[[Right by My Side]]" featuring [[Chris Brown]] were released shortly after.<ref>{{cite web|url=http://www.hiphopdx.com/index/singles/id.19553/title.nicki-minaj-f-2-chainz-beez-in-the-trap|title=Nicki Minaj f. 2 Chainz – Beez In The Trap|date=March 21, 2012|work=HipHopDX|access-date=August 14, 2014|archive-date=March 18, 2015|archive-url=https://web.archive.org/web/20150318164758/http://www.hiphopdx.com/index/singles/id.19553/title.nicki-minaj-f-2-chainz-beez-in-the-trap|url-status=live}}</ref><ref>{{cite web|url=http://www.rap-up.com/2012/03/20/new-music-nicki-minaj-f-chris-brown-right-by-my-side/|title=New Music: Nicki Minaj f/ Chris Brown – 'Right By My Side'|date=March 20, 2012|website=Rap-Up|publisher=Rap-Up, LLC|access-date=August 14, 2014|archive-date=July 31, 2023|archive-url=https://web.archive.org/web/20230731071859/https://www.rap-up.com/2012/03/20/new-music-nicki-minaj-f-chris-brown-right-by-my-side/|url-status=live}}</ref>
''Pink Friday: Roman Reloaded'' was eventually released on April 2, 2012, two months later than initially planned.<ref>{{cite web|url=http://www.digitalspy.com/music/news/a361362/nicki-minaj-delays-new-album-roman-reloaded-release.html#~oMViZuHOyHQY9e|title=Nicki Minaj delays new album 'Roman Reloaded' release|last=Daniels|first=Colin|date=January 21, 2012|work=Digital Spy|access-date=August 14, 2014|archive-date=September 24, 2015|archive-url=https://web.archive.org/web/20150924045232/http://www.digitalspy.com/music/news/a361362/nicki-minaj-delays-new-album-roman-reloaded-release.html#~oMViZuHOyHQY9e|url-status=live}}</ref> It was preceded by the promotional singles "[[Roman in Moscow]]" and "[[Stupid Hoe]]".<ref>{{cite web|url=https://music.apple.com/us/album/roman-in-moscow-single/485755837|title=iTunes Music – Roman In Moscow – Single by Nicki Minaj|work=iTunes Store (US).|publisher=Apple Inc.|date=December 2, 2011|access-date=August 14, 2014|archive-date=August 17, 2018|archive-url=https://web.archive.org/web/20180817124850/https://itunes.apple.com/us/album/roman-in-moscow-single/id485755837|url-status=live}}</ref><ref>{{cite web|url=https://itunes.apple.com/us/album/stupid-hoe-single/id490782635|title=iTunes Music – Stupid Hoe – Single by Nicki Minaj|work=iTunes Store (US)|publisher=Apple Inc.|date=December 20, 2011|access-date=August 14, 2014|archive-date=November 16, 2016|archive-url=https://web.archive.org/web/20161116000122/https://itunes.apple.com/us/album/stupid-hoe-single/id490782635|url-status=dead}}</ref> The latter song was viewed by critics as a diss aimed at Lil' Kim, following her previous attacks on Minaj.<ref>{{cite magazine|title=Lil Kim Addresses Nicki Minaj's 'Stupid Hoe'|url=https://www.billboard.com/music/music-news/lil-kim-addresses-nicki-minajs-stupid-hoe-single-504896/|magazine=Billboard|date=February 16, 2012|access-date=August 21, 2012|archive-date=October 1, 2021|archive-url=https://web.archive.org/web/20211001142833/https://www.billboard.com/articles/columns/the-juice/504896/lil-kim-addresses-nicki-minajs-stupid-hoe-single|url-status=live}}</ref> The album debuted at number one on the ''Billboard'' 200, with first-week sales of 253,000 copies, and was certified four-times platinum by the RIAA in November 2024, becoming Minaj's highest-certified album yet.<ref name="OpeningWeeks" />{{Certification Cite Ref|region=United States|type=album|title=Pink Friday: Roman Reloaded|artist=Nicki Minaj|access-date=November 19, 2024}} However, the mix of hip-hop songs and mainstream pop material received mixed reviews.<ref>{{cite web|url=https://www.huffingtonpost.com/2012/04/02/pink-friday-roman-reloaded-reviews-nicki-minaj_n_1396325.html|title='Pink Friday: Roman Reloaded' Reviews: Nicki Minaj's New Album Gets Mixed Reviews|last=Bell|first=Crystal|date=April 2, 2012|website=[[HuffPost]]|access-date=July 12, 2013|archive-date=June 29, 2012|archive-url=https://web.archive.org/web/20120629231949/http://www.huffingtonpost.com/2012/04/02/pink-friday-roman-reloaded-reviews-nicki-minaj_n_1396325.html|url-status=live}}</ref> "[[Pound the Alarm]]" and "[[Va Va Voom]]" were later released as the final singles from the album.<ref>{{cite news|url=http://www.capitalfm.com/artists/nicki-minaj/news/cover-drive-capitalfm-playlist-15-june-2012/|title=Nicki Minaj And Cover Drive's New Songs Join Capital FM Playlist (15th June)|date=June 15, 2012|work=Capital FM|access-date=August 14, 2014|archive-date=July 31, 2023|archive-url=https://web.archive.org/web/20230731071900/https://www.capitalfm.com/artists/nicki-minaj/news/cover-drive-capitalfm-playlist-15-june-2012/|url-status=live}}</ref><ref>{{cite web|url=http://www.bbc.co.uk/radio1/playlist/|title=12 September 2012|date=September 12, 2012|work=BBC Radio 1 |publisher=BBC|archive-url=https://web.archive.org/web/20120911180402/http://www.bbc.co.uk/radio1/playlist/|archive-date=September 11, 2012|url-status=dead|access-date=August 14, 2014}}</ref> Minaj along with fellow rapper [[M.I.A. (rapper)|M.I.A.]] joined [[Madonna]] to perform the single, "[[Give Me All Your Luvin'|Give Me All Your Luvin]]" during the [[Super Bowl XLVI halftime show]] on February 6, 2012.<ref>{{cite web|url=https://www.bbc.co.uk/news/entertainment-arts-16904511|title=Super Bowl: Madonna gives 'shot of brass'|work=BBC News|date=February 6, 2012|access-date=August 14, 2014|archive-date=July 13, 2023|archive-url=https://web.archive.org/web/20230713051158/https://www.bbc.co.uk/news/entertainment-arts-16904511|url-status=live}}</ref> Minaj was the first solo female rapper to perform at the [[Grammy Award]]s, performing "[[Roman Holiday (song)|Roman Holiday]]" during the [[54th Grammy Awards|2012 ceremony]] on February 12.<ref>{{cite web|url=https://www.bet.com/photo-gallery/piyg7l/nicki-minaj-comes-in-first-a-look-at-her-milestones/nq9dp7|title=Nicki Minaj Comes in First: A Look at Her Milestones|work=BET|access-date=August 14, 2014|archive-date=October 7, 2021|archive-url=https://web.archive.org/web/20211007195843/https://www.bet.com/music/photos/2011/08/nicki-minaj-s-firsts.html#!021212-music-grammys-nicki-minaj-performs-5|url-status=live}}</ref> Her [[exorcism]]-themed performance was controversial, with the [[Catholic League (US)|American Catholic League]] and its president criticizing her performance.<ref>[https://web.archive.org/web/20120611024050/http://www.vibe.com/article/10-most-controversial-moments-nicki-minajs-career-pg-2 "The 10 Most Controversial Moments of Nicki Minaj's Career, Pg. 2."] ''Vibe''. June 5, 2012.</ref><ref name="popescene1">{{cite web|url=http://www.eonline.com/news/293952/nicki-minaj-s-vulgar-grammys-exorcism-angers-catholic-league|title=Nicki Minaj's Vulgar Grammys Exorcism Angers Catholic League|date=February 13, 2012|access-date=May 28, 2018|archive-date=July 31, 2023|archive-url=https://web.archive.org/web/20230731071900/https://www.eonline.com/news/293952/nicki-minaj-s-vulgar-grammys-exorcism-angers-catholic-league|url-status=live}}</ref><ref>[http://www.catholicleague.org/is-nicki-minaj-possessed/ "Is Nicki Minaj Possessed?"] {{Webarchive|url=https://web.archive.org/web/20120417232651/http://www.catholicleague.org/is-nicki-minaj-possessed/ |date=April 17, 2012 }} ''CatholicLeague.org''. The Catholic League. February 13, 2012.</ref>
Minaj began her headlining [[Pink Friday Tour]] on May 16, 2012, which was followed by the [[Pink Friday: Reloaded Tour]] beginning October 14, 2012.<ref>{{cite web |last=Wass |first=Mike |date=May 16, 2012 |title=Nicki Minaj's Pink Friday World Tour Pops Open In Sydney: Concert Review |url=http://www.idolator.com/6470632/nicki-minaj-pink-friday-tour-sydney-photos-review |access-date=August 14, 2014 |work=Idolator |publisher=Spin Media |archive-date=March 5, 2024 |archive-url=https://web.archive.org/web/20240305180339/https://www.idolator.com/6470632/nicki-minaj-pink-friday-tour-sydney-photos-review |url-status=dead }}</ref><ref>{{cite web|url=http://www.digitalspy.com/music/news/a391749/nicki-minaj-extends-uk-arena-tour.html#~oMVqc95ftHv2bo|title=Nicki Minaj extends UK arena tour|last=Daniels|first=Colin|work=Digital Spy|date=July 5, 2012|access-date=August 14, 2014|archive-date=December 27, 2014|archive-url=https://web.archive.org/web/20141227031117/http://www.digitalspy.com/music/news/a391749/nicki-minaj-extends-uk-arena-tour.html#~oMVqc95ftHv2bo|url-status=live}}</ref> Although she was scheduled to headline the June 3 [[WQHT|Hot 97]] [[Summer Jam (festival)|Summer Jam]] at [[MetLife Stadium]] in [[New Jersey]], at the request of [[Lil Wayne]] she canceled her appearance the day of the show after [[Peter Rosenberg]] of the station dismissed her single "Starships" as "not real hip-hop".<ref>{{cite web|url=http://okmagazine.com/get-scoop/*nicki-minaj-defends-decision-drop-out-hot-97-summer-jam-concert-lil-wayne-radio/|title=Nicki Minaj Defends Her Decision to Drop Out of Hot 97 Summer Jam Concert|website=OK!|publisher=Northern & Shell|date=June 5, 2012|access-date=August 14, 2014|archive-date=May 29, 2016|archive-url=https://web.archive.org/web/20160529021810/http://okmagazine.com/get-scoop/*nicki-minaj-defends-decision-drop-out-hot-97-summer-jam-concert-lil-wayne-radio/|url-status=live}}</ref> The following month, she voiced [[List of Ice Age characters#Steffie|Steffie]] in the animated film ''[[Ice Age: Continental Drift]]'' (2012).<ref>{{cite web|url=http://www.digitalspy.com/movies/news/a391614/nicki-minaj-ice-age-4-is-a-blessing-and-an-honor.html#~oMVE362D0d4tMp|title=Nicki Minaj: 'Ice Age 4 is a blessing and an honor'|last=Harp|first=Justin|work=Digital Spy|date=July 4, 2012|access-date=August 14, 2014|archive-date=September 24, 2015|archive-url=https://web.archive.org/web/20150924015828/http://www.digitalspy.com/movies/news/a391614/nicki-minaj-ice-age-4-is-a-blessing-and-an-honor.html#~oMVE362D0d4tMp|url-status=live}}</ref> Minaj won awards for [[Best Female Video]] (for "Starships") at the [[2012 MTV Video Music Awards]] and [[MTV Europe Music Award for Best Hip-Hop|Best Hip-Hop]] at the [[2012 MTV Europe Music Awards]].<ref>{{cite web|url=http://www.digitalspy.com/music/news/a437337/mtv-emas-2012-winners-taylor-swift-one-direction-bieber-celebrate.html#~oMVrxeHJmt310F|title=MTV EMAs 2012 winners: Taylor Swift, One Direction, Bieber celebrate|last=Rigby|first=Sam|work=Digital Spy|date=November 11, 2012|access-date=August 14, 2014|archive-date=May 13, 2014|archive-url=https://web.archive.org/web/20140513120733/http://www.digitalspy.com/music/news/a437337/mtv-emas-2012-winners-taylor-swift-one-direction-bieber-celebrate.html#~oMVrxeHJmt310F|url-status=live}}</ref>
An expanded version of ''Pink Friday: Roman Reloaded'', subtitled ''[[Pink Friday: Roman Reloaded – The Re-Up|The Re-Up]]'', was released on November 19, 2012, with seven new songs.<ref>{{cite web|url=https://music.apple.com/us/album/pink-friday-roman-reloaded/575727176|archive-url=https://web.archive.org/web/20140602105529/https://itunes.apple.com/us/album/pink-friday-roman-reloaded/id575727176|archive-date=June 2, 2014|title=iTunes Music – Pink Friday Roman Reloaded the Re-Up by Nicki Minaj|work=iTunes Store (US) |publisher=Apple Inc.|date=November 19, 2012|url-status=live|access-date=August 14, 2014}}</ref> That month, she was the subject of a three-part [[E!]] documentary titled ''Nicki Minaj: My Truth''.<ref name="voomvideo">{{cite web|url=http://www.eonline.com/news/357729/nicki-minaj-s-quot-va-va-voom-quot-music-video-premiere-mdash-watch-now?dlvrit=48939|title=Nicki Minaj's 'Va Va Voom' Music Video Premiere|last=Serpe|first=Gina|publisher=E!|date=October 26, 2012|access-date=August 14, 2014|archive-date=December 6, 2022|archive-url=https://web.archive.org/web/20221206185043/https://www.eonline.com/news/357729/nicki-minaj-s-quot-va-va-voom-quot-music-video-premiere-mdash-watch-now?dlvrit=48939|url-status=live}}</ref> In September, Minaj joined the judges' panel for the [[American Idol (season 12)|twelfth season]] of ''[[American Idol]]'' with [[Mariah Carey]], [[Keith Urban]], and [[Randy Jackson]].<ref>{{cite magazine|url=https://www.rollingstone.com/music/news/nicki-minaj-and-keith-urban-confirmed-as-american-idol-judges-20120916|title=Nicki Minaj and Keith Urban Confirmed As American Idol Judges|magazine=Rolling Stone|publisher=Wenner Media, LLC|date=September 16, 2012|access-date=August 14, 2014|archive-date=March 27, 2014|archive-url=https://web.archive.org/web/20140327054948/http://www.rollingstone.com/music/news/nicki-minaj-and-keith-urban-confirmed-as-american-idol-judges-20120916|url-status=live}}</ref> Throughout the show there were disagreements between Carey and Minaj, with both of them leaving the series at the end of the season.<ref>{{Cite web|url=http://www.mtv.com/news/1700186/nicki-minaj-mariah-carey-american-idol-beef/|title=Nicki Minaj, Mariah Carey: A Timeline To Their 'American Idol' Beef|first=Jocelyn|last=Vena|publisher=MTV News|access-date=November 26, 2019|archive-date=September 10, 2019|archive-url=https://web.archive.org/web/20190910142240/http://www.mtv.com/news/1700186/nicki-minaj-mariah-carey-american-idol-beef/|url-status=dead}}</ref><ref>{{Cite web|url=https://www.eonline.com/fr/news/397705/mariah-carey-and-nicki-minaj-s-american-idol-feud-5-best-moments|title=Mariah Carey and Nicki Minaj's American Idol Feud: 5 Best Moments|date=March 14, 2013|website=E! News|access-date=November 26, 2019|archive-date=March 5, 2024|archive-url=https://web.archive.org/web/20240305180411/https://www.eonline.com/news/397705/mariah-carey-and-nicki-minaj-s-american-idol-feud-5-best-moments|url-status=live}}</ref><ref>{{cite web|url=https://www.hollywoodreporter.com/idol-worship/nicki-minaj-slams-mariah-carey-451012|title=Nicki Minaj Slams Mariah Carey as 'Bitter,' 'Insecure'|last=Carlson|first=Erin|website=The Hollywood Reporter|date=May 2, 2013|access-date=August 14, 2014|archive-date=May 20, 2013|archive-url=https://web.archive.org/web/20130520055608/http://www.hollywoodreporter.com/idol-worship/nicki-minaj-slams-mariah-carey-451012|url-status=live}}</ref><ref>{{cite magazine|url=https://www.rollingstone.com/music/news/nicki-minaj-and-mariah-carey-leaving-american-idol-20130531|title=Nicki Minaj and Mariah Carey Leaving 'American Idol'|magazine=Rolling Stone|publisher=Wenner Media, LLC|date=May 31, 2013|access-date=August 14, 2014|archive-date=February 22, 2018|archive-url=https://web.archive.org/web/20180222225712/https://www.rollingstone.com/music/news/nicki-minaj-and-mariah-carey-leaving-american-idol-20130531|url-status=live}}</ref> ''Pink Friday: Roman Reloaded'' was the best-selling rap album released in 2012 in the US.<ref>{{cite web|title=Infographic - Precious Medals: The 2012 Hip Hop Gold & Platinum Plaques|work=HipHopDX |date=January 3, 2013|url=http://www.hiphopdx.com/index/editorials/id.2019/title.infographic--precious-medals-the-2012-hip-hop-gold-platinum-plaques|access-date=January 3, 2013|archive-url=https://web.archive.org/web/20150516063547/http://www.hiphopdx.com/index/editorials/id.2019/title.infographic--precious-medals-the-2012-hip-hop-gold-platinum-plaques|archive-date=May 16, 2015|url-status=live}}</ref><ref>{{cite news|last=Mitchell|first=Gail|title=The Year In R&B/Hip-Hop 2012: Drake, Nicki Minaj Among Year's Chart Champs|url=https://www.billboard.com/articles/news/1481409/the-year-in-rbhip-hop-2012-drake-nicki-minaj-among-years-chart-champs|magazine=Billboard|access-date=July 21, 2013|archive-url=https://web.archive.org/web/20210501013448/https://www.billboard.com/articles/news/1481409/the-year-in-rbhip-hop-2012-drake-nicki-minaj-among-years-chart-champs|archive-date=May 1, 2021|url-status=live}}</ref> It was also the 25th-best-selling album globally in 2012, with over 1.4 million copies sold worldwide, according to the [[International Federation of the Phonographic Industry]] (IFPI).<ref>{{cite web|title=The Global Bestsellers of 2012|url=http://www.aria.com.au/documents/RIN2013.pdf|publisher=[[International Federation of the Phonographic Industry]]|url-status=dead|archive-url=https://web.archive.org/web/20130409080309/http://www.aria.com.au/documents/RIN2013.pdf|archive-date=April 9, 2013}}</ref> In 2013, Minaj became the most-charted female rapper on the ''Billboard'' Hot 100 at the time, with 44 entries, and tied Carey as seventh among female artists.<ref>{{cite magazine|url=https://www.billboard.com/pro/weekly-chart-notes-nicki-minaj-matches-mariah-careys-hot-100/|title=Weekly Chart Notes: Nicki Minaj Matches Mariah Carey's Hot 100 Mark|magazine=Billboard|date=April 12, 2013|access-date=August 14, 2014|archive-date=July 16, 2014|archive-url=https://web.archive.org/web/20140716103422/http://www.billboard.com/articles/columns/chart-beat/1557296/weekly-chart-notes-nicki-minaj-matches-mariah-careys-hot-100|url-status=live}}</ref>
=== 2014–2017: ''The Pinkprint'' and acting ventures ===
{{Further|The Pinkprint}}
Minaj's first live-action theatrical film ''[[The Other Woman (2014 film)|The Other Woman]]'' was filmed in spring 2013 and premiered on April 25, 2014.<ref>{{cite web | title = Nicki Minaj to make movie debut in new Cameron Diaz comedy | website =NME | date =April 29, 2013 | url =https://www.nme.com/filmandtv/news/nicki-minaj-to-make-movie-debut-in-new-cameron-diaz/306761 | access-date =November 20, 2013 | archive-date =October 18, 2013 | archive-url = https://web.archive.org/web/20131018014120/http://www.nme.com/filmandtv/news/nicki-minaj-to-make-movie-debut-in-new-cameron-diaz/306761 | url-status =live }}</ref> As she was working on her third studio album ''The Pinkprint'', she described the album as "a continuation of ''[[Pink Friday: Roman Reloaded – The Re-Up|The Re-Up]]'' with a lot more" and said it would focus on her "hip hop roots".<ref>{{cite magazine |last=Kreps |first=Daniel |title=Watch Nicki Minaj's Breakup-Inspired 'Pinkprint Movie' |magazine=Rolling Stone |url=https://www.rollingstone.com/music/videos/nicki-minaj-breakup-inspired-pinkprint-movie-20141220 |access-date=December 20, 2014 |archive-date=April 29, 2016 |archive-url=https://web.archive.org/web/20160429222155/http://www.rollingstone.com/music/videos/nicki-minaj-breakup-inspired-pinkprint-movie-20141220 |url-status=dead }}</ref><ref>{{cite magazine | last =Ramirez | first =Erika | title =Nicki Minaj Will 'Focus On Rap' For Upcoming Album | magazine = [[Billboard (magazine)|Billboard]] | date =May 28, 2013 | url = https://www.billboard.com/music/rb-hip-hop/nicki-minaj-will-focus-on-rap-for-upcoming-album-1565003/ | access-date =November 20, 2013 | archive-date =September 14, 2013 | archive-url =https://web.archive.org/web/20130914152146/http://www.billboard.com/articles/columns/the-juice/1565003/nicki-minaj-will-focus-on-rap-for-upcoming-album | url-status =live }}</ref><ref>{{cite web|last=Loren|first=Arielle|title=Nicki Minaj Says Third Album Will Be an Extension of The Re-Up| url= https://www.bet.com/article/hqzwvi/nicki-minaj-third-album-will-be-an-extension-of-the-re-up|website=BET.com |access-date=January 11, 2013|date=November 30, 2012| archive-date= March 3, 2016|archive-url= https://web.archive.org/web/20160303190733/http://www.bet.com/news/music/2012/11/30/nicki-minaj-announces-third-album-as-continuation-of-the-re-up.html|url-status=live}}</ref> During an [[MTV]] interview, she said that she has "much to talk about" in the album.<ref>{{cite web | last = Markham | first =Rob | title =Nicki Minaj 'Delving Into Deep Sh--' on 2014 Album | publisher =MTV | date =November 4, 2013 | url = http://www.mtv.com/news/articles/1716802/nicki-minaj-new-music-2014.jhtml | access-date =November 20, 2013 | archive-date =March 27, 2014 | archive-url = https://web.archive.org/web/20140327080824/http://www.mtv.com/news/articles/1716802/nicki-minaj-new-music-2014.jhtml | url-status =dead }}</ref>
"[[Pills n Potions]]" was released as the lead single from ''The Pinkprint'' in May 2014.<ref>{{cite magazine |url=https://www.billboard.com/articles/events/bbma-2014/6092077/nicki-minaj-single-title-pinkprint-pills-potions-new-album |title=Nicki Minaj Reveals 'Pills N Potions' Single at Billboard Music Awards |magazine= Billboard |access-date=April 16, 2020 |archive-date=May 19, 2022 |archive-url=https://web.archive.org/web/20220519164109/https://www.billboard.com/music/music-news/nicki-minaj-single-title-pinkprint-pills-potions-new-album-6092077/ |url-status=live }}</ref> In July, she appeared on the song "[[Bang Bang (Jessie J, Ariana Grande and Nicki Minaj song)|Bang Bang]]" with singers [[Jessie J]] and [[Ariana Grande]], which peaked at number three on the US ''Billboard'' Hot 100<ref name="bangbangdiamond">{{Cite web |last=Trapp |first=Malcolm |date=May 23, 2024 |title=Jessie J, Nicki Minaj, And Ariana Grande's "Bang Bang" Becomes First All-Female Collaboration To Be RIAA-Certified Diamond |url=https://www.rap-up.com/2024/05/23/nicki-minaj-earns-her-second-diamond-certification-with-bang-bang/ |access-date=June 6, 2024 |website=[[Rap-Up]] |archive-date=May 25, 2024 |archive-url=https://web.archive.org/web/20240525174028/https://www.rap-up.com/2024/05/23/nicki-minaj-earns-her-second-diamond-certification-with-bang-bang/ |url-status=live }}</ref> and marked Minaj's first number-one on the [[UK singles chart]].<ref>{{Cite web |last=Martin |first=Liam |date=September 28, 2014 |title=Jessie J, Ariana Grande and Nicki Minaj top the chart with 'Bang Bang' |url=https://www.digitalspy.com/music/uk-singles-chart/a599666/jessie-j-ariana-grande-and-nicki-minaj-top-the-chart-with-bang-bang/ |access-date=June 6, 2024 |website=[[Digital Spy]] |archive-date=June 6, 2024 |archive-url=https://web.archive.org/web/20240606142735/https://www.digitalspy.com/music/uk-singles-chart/a599666/jessie-j-ariana-grande-and-nicki-minaj-top-the-chart-with-bang-bang/ |url-status=live }}</ref> "Bang Bang" was certified diamond by the RIAA in May 2024, for recognized sales of 10 million units; it became the first all-female collaboration to achieve the certification.<ref name="bangbangdiamond"/> Its music video garnered over two billion views on YouTube.<ref>{{Cite magazine |last=Kaufman |first=Gil |date=July 3, 2024 |title=Jessie J, Ariana Grande and Nicki Minaj's 'Bang Bang' Notches Two Billion YouTube Views |url=https://www.billboard.com/music/rb-hip-hop/jessie-j-ariana-grande-nicki-minaj-bang-bang-two-billion-youtube-views-1235724269/ |access-date=August 23, 2024 |magazine=Billboard |archive-date=August 23, 2024 |archive-url=https://web.archive.org/web/20240823180837/https://www.billboard.com/music/rb-hip-hop/jessie-j-ariana-grande-nicki-minaj-bang-bang-two-billion-youtube-views-1235724269/ |url-status=live }}</ref> "[[Anaconda (song)|Anaconda]]" was released in August as the second single from the album, peaking at number two, which became her highest-charting single in the US at the time.<ref name="anacondan2hot100">{{cite magazine |last1=Trust |first1=Gary |date=August 27, 2014 |title=Taylor Swift's 'Shake It Off' Debuts At No. 1 On Hot 100 |url=http://www.billboard.com/articles/news/6229439/taylor-swift-shake-it-off-no-1-hot-100-nicki-minaj-anaconda |url-access=subscription |access-date=June 6, 2024 |magazine=[[Billboard (magazine)|Billboard]] |archive-date=September 21, 2014 |archive-url=https://web.archive.org/web/20140921040138/http://www.billboard.com/articles/news/6229439/taylor-swift-shake-it-off-no-1-hot-100-nicki-minaj-anaconda |url-status=live }}</ref><ref>{{cite magazine|title=Nicki Minaj Previews Steamy 'Anaconda' Video, Unleashes Full Track|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-previews-steamy-anaconda-video-unleashes-full-track-6206907/ |magazine= Billboard|access-date=August 29, 2014|archive-date=August 7, 2014|archive-url=https://web.archive.org/web/20140807045328/http://www.billboard.com/articles/columns/the-juice/6206907/nicki-minaj-previews-steamy-anaconda-video-unleashes-full-track|url-status=live}}</ref> Its ascent from number 39 to number two in its third week marked the second-biggest jump within the chart's top 40 region, at the time.<ref name="anacondan2hot100"/> The music video for "Anaconda" went [[Viral video|viral]] upon release online; it set a 24-hour [[Vevo]] record, accumulating 19.6 million views on its first day of release, breaking the record previously held by [[Miley Cyrus]]'s "[[Wrecking Ball (Miley Cyrus song)|Wrecking Ball]]".<ref>{{cite magazine|title=Nicki Minaj's 'Anaconda' Video Breaks Vevo Record|url=https://www.billboard.com/articles/news/6229110/nicki-minaj-anaconda-vevo-record|magazine=Billboard|date=August 22, 2014|access-date=August 29, 2014|archive-date=August 28, 2014|archive-url=https://web.archive.org/web/20140828204339/http://www.billboard.com/articles/news/6229110/nicki-minaj-anaconda-vevo-record|url-status=live}}</ref> It surpassed one billion views on YouTube. On November 9, 2014, Minaj hosted the [[2014 MTV Europe Music Awards]] in Glasgow, Scotland.<ref name=":1">{{Cite news |title=Nicki Minaj Dropped Endless F-Bombs While Hosting The 2014 MTV EMA |language=en |publisher=MTV |url=http://www.mtv.com/news/1992293/nicki-minaj-2014-ema-host-cursing/ |access-date=February 27, 2018 |archive-date=May 25, 2022 |archive-url=https://web.archive.org/web/20220525182546/http://www.mtv.com/news/1992293/nicki-minaj-2014-ema-host-cursing/ |url-status=dead }}</ref> She also won the Best Hip-Hop Award for a second time.<ref name=":1" />
''The Pinkprint'' was officially released on December 15, 2014, and debuted at number two in the U.S., with first week sales of 244,000 equivalent units (198,000 in pure album sales and 46,000 combined album-equivalent units and [[Streaming media|streams]]).<ref>{{cite magazine |last=Caulfield |first=Keith |date=December 24, 2014 |title=Taylor Swift Back at No. 1 on Billboard 200, Nicki Minaj Debuts at No. 2 |url=https://www.billboard.com/pro/taylor-swift-billboard-200-nicki-minaj/ | magazine= Billboard |access-date=May 21, 2015 |archive-date=May 6, 2022 |archive-url=https://web.archive.org/web/20220506174240/https://www.billboard.com/pro/taylor-swift-billboard-200-nicki-minaj/ |url-status=live }}</ref><ref>{{cite magazine |last=Mendizabal |first=Amaya |date=December 26, 2014 |title=Nicki Minaj's 'The Pinkprint' Debuts at No. 1 on Top R&B/Hip-Hop Albums |url=https://www.billboard.com/pro/nicki-minaj-pinkprint-debuts-no-1-top-rbhip-hop-albums/ | magazine= Billboard |access-date=October 27, 2015 |archive-date=October 15, 2015 |archive-url=https://web.archive.org/web/20151015125046/http://www.billboard.com/articles/columns/chart-beat/6422239/nicki-minaj-pinkprint-debuts-no-1-top-rbhip-hop-albums |url-status=live }}</ref> Upon release, critics praised the production and personal lyrics.<ref>{{cite web |title=Reviews for The Pinkprint by Nicki Minaj |url=https://www.metacritic.com/music/the-pinkprint/nicki-minaj |access-date=December 15, 2014 |publisher=[[Metacritic]] |archive-date=December 19, 2014 |archive-url=https://web.archive.org/web/20141219024708/http://www.metacritic.com/music/the-pinkprint/nicki-minaj |url-status=live }}</ref> At the [[58th Annual Grammy Awards|58th Grammy Awards]], Minaj received three more Grammy Award nominations, including a second Best Rap Album nomination for ''The Pinkprint'', a [[Grammy Award for Best Rap Song|Best Rap Song]] nomination for "Anaconda" and a [[Grammy Award for Best Pop Duo/Group Performance|Best Pop Duo/Group Performance]] nomination for "Bang Bang".<ref>{{Cite news |date=November 28, 2017 |title=Nicki Minaj |language=en |publisher= National Academy of Recording Arts and Sciences | url= https://www.grammy.com/artists/nicki-minaj/4579 |access-date=February 27, 2018 |archive-date=June 27, 2020 |archive-url= https://web.archive.org/web/20200627034621/https://www.grammy.com/grammys/artists/nicki-minaj |url-status=live }}</ref><ref>{{cite news| url= https://www.latimes.com/entertainment/music/la-et-ms-grammys-2015-nominees-winners-list-story.html#page=1 |title= Grammys 2015: List of nominees| archiveurl= https://web.archive.org/web/20151113092219/http://www.latimes.com/entertainment/music/la-et-ms-grammys-2015-nominees-winners-list-story.html#page=1 |archivedate=November 13, 2015 |work= [[Los Angeles Times]]| date= December 5, 2014| access-date= }}</ref> In March 2015, Minaj embarked on her third world tour entitled [[The Pinkprint Tour]] and also became the first female artist to chart four songs simultaneously in the top 10 of ''Billboard''{{'}}s Mainstream R&B/Hip-Hop airplay chart.<ref>{{cite magazine |title=Nicki Minaj the First Woman With 4 Simultaneous Top 10 Hits on Mainstream R&B/Hip-Hop Chart |url=https://www.billboard.com/pro/nicki-minaj-chart-history-rb-hip-hop/ |magazine= Billboard |access-date=March 18, 2015 |archive-date=March 19, 2015 |archive-url= https://web.archive.org/web/20150319153919/http://www.billboard.com/articles/columns/chart-beat/6502399/nicki-minaj-chart-history-rb-hip-hop |url-status=live }}</ref> At the [[2015 BET Awards]], Minaj won her sixth consecutive award for [[BET Award for Best Hip-Hop Artists|Best Female Hip-Hop Artist]], becoming the female rapper with most wins in that category.<ref>{{cite web |title=Nicki Minaj wins Best Female Hip-Hop Artist at the 2015 BET Awards |url=http://www.xxlmag.com/news/2015/06/nicki-minaj-best-female-hip-hop-artist-2015-bet-awards/ |work=[[XXL Magazine]] |date=June 29, 2015 |access-date=September 16, 2015 |archive-date=March 4, 2016 |archive-url= https://web.archive.org/web/20160304074920/http://www.xxlmag.com/news/2015/06/nicki-minaj-best-female-hip-hop-artist-2015-bet-awards/ |url-status=live }}</ref> In August 2015, [[Madame Tussauds Las Vegas|Madame Tussauds]] unveiled a [[Wax sculpture|wax figure]] of Minaj, which depicted a pose from the "Anaconda" music video.<ref>{{Cite web |url= https://www.madametussauds.com/las-vegas/en/whats-inside/music-icons/nicki-minaj/|title= Nicki Minaj wax figure| website= madametussauds.com| publisher= Madame Tussauds Las Vegas |access-date=October 26, 2018|archive-date=October 27, 2018|archive-url=https://web.archive.org/web/20181027021646/https://www.madametussauds.com/las-vegas/en/whats-inside/music-icons/nicki-minaj/|url-status=live}}</ref><ref>{{Cite news|url=https://www.vulture.com/2015/08/nicki-minaj-madame-tussauds-wax-figure.html|title=Here Is the Unfortunate Tale of Nicki Minaj's Madame Tussauds Wax Figure|work=Vulture|access-date=October 26, 2018|language=en|archive-date=October 27, 2018|archive-url= https://web.archive.org/web/20181027061523/http://www.vulture.com/2015/08/nicki-minaj-madame-tussauds-wax-figure.html|url-status=live}}</ref> Minaj voiced her approval of the wax figure on social media.<ref name=":7">{{Cite news|url=https://www.thefader.com/2015/08/19/nicki-minaj-wax-figure-security|title=Nicki Minaj's Wax Figure Gets Extra Security And The Museum Explains Why They Chose That Pose|work=The FADER|access-date=October 26, 2018|language=en|archive-date=October 27, 2018|archive-url= https://web.archive.org/web/20181027021656/https://www.thefader.com/2015/08/19/nicki-minaj-wax-figure-security|url-status=live}}</ref> The wax figure received criticism from some, including Angharad Welsh of ''[[The Independent]]'', who called it "a sexist, racist mistake".<ref>{{Cite news|url=https://www.independent.co.uk/voices/azealia-banks-was-right-about-the-nicki-minaj-waxwork-its-a-sexist-racist-mistake-10464009.html |archive-url=https://ghostarchive.org/archive/20220620/https://www.independent.co.uk/voices/azealia-banks-was-right-about-the-nicki-minaj-waxwork-its-a-sexist-racist-mistake-10464009.html |archive-date=June 20, 2022 |url-access=subscription |url-status=live|title=Azealia Banks was right about the Nicki Minaj waxwork: it's a sexist, racist mistake|work=The Independent|access-date=October 26, 2018|language=en-GB}}</ref><ref>{{Cite news |url=https://www.elitedaily.com/life/minaj-statue-female-positivity/1185371|title=Nicki Minaj Owned Her Sexuality And People Tried To Take It From Her |last= LaFata |first= Alexia |work= Elite Daily|access-date=October 26, 2018|language=en|archive-date=October 27, 2018|archive-url= https://web.archive.org/web/20181027021714/https://www.elitedaily.com/life/minaj-statue-female-positivity/1185371|url-status=live}}</ref> [[File:Nicki Minaj interview 2016.jpg|thumb|Minaj in a 2016 interview|left|upright]]
In May 2015, it was announced that Minaj would feature in the third installment of the ''[[Barbershop (film series)|Barbershop]]'' film series, alongside [[Ice Cube]], [[Cedric the Entertainer]], [[Eve (rapper)|Eve]], and other original cast members.<ref name="BB:Bshop2">{{cite magazine|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-barbershop-3-6554008/|title=Nicki Minaj Headed for the Big Screen in 'Barbershop 3'|last1=Weiner|first1=Natalie| magazine= Billboard |access-date=April 18, 2016|archive-date=May 4, 2016| archive-url=https://web.archive.org/web/20160504080252/http://www.billboard.com/articles/columns/the-juice/6554008/nicki-minaj-barbershop-3|url-status=live}}</ref> Titled ''[[Barbershop: The Next Cut]]'', the film was released on April 15, 2016. For Minaj's performance as a "sassy" hairdresser named Draya, she was nominated for a [[Teen Choice Awards|Teen Choice Award]] for [[Teen Choice Award for Choice Movie Actress – Comedy|Choice Movie Actress: Comedy]].<ref name="BB:Bshop2" /><ref name= "TeenChoice">{{cite web| url= http://www.eonline.com/news/767635/teen-choice-awards-2016-nominations-announced-see-the-first-wave-of-potential-winners|title=Teen Choice Awards 2016 Nominations Announced: See the 'First Wave' of Potential Winners|last=Vulpo|first=Mike|date=May 24, 2016 |publisher=[[E!]]|archive-url= https://web.archive.org/web/20160526090121/http://www.eonline.com/news/767635/teen-choice-awards-2016-nominations-announced-see-the-first-wave-of-potential-winners|archive-date=May 26, 2016| url-status=live|access-date=May 25, 2016}}</ref> In September 2015, it was announced that Minaj would executive produce and appear in a scripted [[Single-camera setup|single-camera]] comedy series for ABC Family (now [[Freeform (TV channel)|Freeform]]) based on her life growing up in [[Queens]], New York City.<ref name= "DeadlineNickiSeries2">{{cite web |last1=Andreeva |first1=Nellie |date=September 29, 2015 |title=Nicki Minaj Comedy Gets ABC Family Series Commitment |url= https://deadline.com/2015/09/nicki-minaj-comedy-series-abc-family-1201554286/ |access-date=April 18, 2016 |website=[[Deadline Hollywood]] |archive-date=April 16, 2016 |archive-url= https://web.archive.org/web/20160416114931/http://deadline.com/2015/09/nicki-minaj-comedy-series-abc-family-1201554286/ |url-status=live }}</ref> The show was titled ''Nicki'' and the pilot episode was filmed in Minaj's hometown in January 2016.<ref name="DeadlineNickiSeries2" /> In October 2016, Minaj stated the filming was postponed for undisclosed reasons.<ref>{{cite web |date=October 17, 2016 |title=Nicki Minaj's ABC Show Postponed |url=https://pitchfork.com/news/69066-nicki-minajs-abc-show-postponed/ |access-date=October 18, 2016 |website=[[Pitchfork (website)|Pitchfork]] |archive-date=October 17, 2016 |archive-url=https://web.archive.org/web/20161017203302/http://pitchfork.com/news/69066-nicki-minajs-abc-show-postponed/ |url-status=live }}</ref>
In February 2017, Minaj featured on [[Gucci Mane]]'s song "Make Love" and [[Jason Derulo]]'s single "[[Swalla]]" alongside [[Ty Dolla Sign]], the latter of which reached the top ten in several countries, including a peak of number six in the UK.<ref>{{cite web |last1=Nelson |first1=Jeff |title=Jason Derulo on Nicki Minaj-Remy Ma Feud, "Swalla": I Don't Know That It's a Diss |url=https://people.com/music/jason-derulo-didnt-know-nicki-minaj-remy-ma-feud-swalla/ |website=[[People (magazine)|People]] |access-date=December 9, 2020 |date=March 22, 2017|archive-url=https://web.archive.org/web/20170325004836/https://people.com/music/jason-derulo-didnt-know-nicki-minaj-remy-ma-feud-swalla/|archive-date=March 25, 2017|url-status=dead}}</ref><ref>{{cite web|url=http://www.officialcharts.com/charts/singles-chart/20170303/7501/|title=Official Singles Chart Top 100|publisher= Official Charts Company |access-date= March 18, 2017|archive-date=May 30, 2017|archive-url=https://web.archive.org/web/20170530135846/http://www.officialcharts.com/charts/singles-chart/20170303/7501/|url-status= live}}</ref> Rapper [[Remy Ma]] released the [[diss track]] "[[Shether]]", which, among other claims and insults, alleged that Minaj used a [[ghostwriter]] and was in a [[360 deal|360-record deal]]. The track's release followed interviews where Remy Ma had praised Minaj, rapped over a couple of Minaj's songs, and released tracks that media interpreted as digs at Minaj.<ref name="bbtl">{{cite magazine |last1=Lamarre |first1=Carl |title=A Timeline of Nicki Minaj & Remy Ma's Rocky Relationship |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-remy-ma-feud-timeline-7702273/ |magazine=[[Billboard (magazine)|Billboard]] |access-date=December 9, 2020 |language=en |date=February 28, 2017 |archive-date=December 9, 2020 |archive-url=https://web.archive.org/web/20201209054242/https://www.billboard.com/articles/columns/hip-hop/7702273/nicki-minaj-remy-ma-feud-timeline |url-status=live }}</ref><ref>{{cite web |last1=Simmons |first1=Ted |title=Is Remy Ma Throwing Shots at Nicki Minaj in Phresher's 'Wait a Minute (Remix)' Video? - XXL |url=https://www.xxlmag.com/remy-ma-nicki-minaj-phresher-wait-a-minute-remix-video/ |website=[[XXL (magazine)|XXL]] |access-date=December 8, 2020 |language=en |date=December 27, 2016 |archive-date=September 15, 2024 |archive-url=https://web.archive.org/web/20240915134737/https://www.xxlmag.com/remy-ma-nicki-minaj-phresher-wait-a-minute-remix-video/ |url-status=live }}</ref> Remy Ma also released a second diss track aimed at Minaj, "Another One". Minaj responded in her single "[[No Frauds]]", featuring Drake and Lil Wayne, in which, among other digs, she took shots at Remy Ma's prison stint for assault, called her a "fraud committin' perjury", and accused her of using a ghostwriter, referring to her husband, rapper [[Papoose (rapper)|Papoose]]; Minaj also rapped "I never signed a 360, bitch you wild dumb". On her Instagram post promoting the song, Minaj wrote: "Great diss records can't be lies. Great diss records are FACTS ... Been writing my own raps since I was 11."<ref>{{cite web|url=http://people.com/music/nicki-minaj-new-music/|title=Nicki Minaj Responds to Remy Ma's Diss Track with New Singles and a Challenge: 'You Got 72 Hours to Drop a Hit'|work=People|date=March 10, 2017|access-date=September 5, 2024|archive-url=https://web.archive.org/web/20170326131647/http://people.com/music/nicki-minaj-new-music/|archive-date=March 26, 2017|url-status=live}}</ref> "No Frauds" was one of three songs Minaj released simultaneously, along with "[[Changed It]]" and "[[Regret in Your Tears]]".<ref name="nbcnews.com">{{cite web |last1=Duster |first1=Chandelis R. |title=Nicki Minaj claps back at Remy Ma with new song 'No Frauds' |url=https://www.nbcnews.com/news/nbcblk/nicki-minaj-claps-back-remy-ma-no-frauds-n731946 |publisher=[[NBC News]] |access-date=December 9, 2020 |date=March 10, 2017 |archive-date=November 12, 2020 |archive-url=https://web.archive.org/web/20201112042743/https://www.nbcnews.com/news/nbcblk/nicki-minaj-claps-back-remy-ma-no-frauds-n731946 |url-status=live }}</ref><ref>{{cite web |last1=Jensen |first1=Erin |title=Nicki Minaj fans the Remy Ma feud flames with 'No Frauds' |url=https://www.usatoday.com/story/life/entertainthis/2017/03/10/nicki-minaj-remy-ma-no-frauds-diss-track/98998568/ |website=USA Today |access-date=December 9, 2020 |date=March 10, 2017 |archive-date=February 13, 2020 |archive-url=https://web.archive.org/web/20200213065009/https://www.usatoday.com/story/life/entertainthis/2017/03/10/nicki-minaj-remy-ma-no-frauds-diss-track/98998568/ |url-status=live }}</ref> On March 20, 2017, with the three songs she released, Minaj broke the record for the most ''Billboard'' Hot 100 entries for a female artist, which at the time was previously held by [[Aretha Franklin]].<ref>{{Cite magazine|url=https://www.billboard.com/pro/nicki-minaj-passes-aretha-franklin-for-most-billboard-hot-100/|title=Nicki Minaj Passes Aretha Franklin for Most Billboard Hot 100 Hits of Any Female Artist|magazine=Billboard| access-date= January 17, 2018|archive-date=November 4, 2021|archive-url=https://web.archive.org/web/20211104091522/https://www.billboard.com/articles/columns/chart-beat/7728788/nicki-minaj-passes-aretha-franklin-for-most-billboard-hot-100|url-status=live}}</ref> That month, Minaj also signed with the major modeling agency, [[Wilhelmina Models]].<ref>{{cite news|url=https://www.vogue.com/article/nicki-minaj-signs-modeling-contract-wilhelmina|title=Nicki Minaj Just Signed a Major Modeling Contract|work=Vogue|date=March 28, 2017|access-date=April 1, 2017|archive-date=July 18, 2023|archive-url=https://web.archive.org/web/20230718165106/https://www.vogue.com/article/nicki-minaj-signs-modeling-contract-wilhelmina|url-status=live}}</ref> Minaj had previously modelled for and been the face of several high-profile designer brands before signing with Wilhelmina Models.
In May 2017, Minaj opened the [[2017 Billboard Music Awards|2017 ''Billboard'' Music Awards]] with a medley performance that was described by Elias Leight of ''[[Rolling Stone]]'' as "flamboyantly produced" and "dexterous".<ref>{{cite magazine| url= https://www.rollingstone.com/music/news/nicki-minaj-opens-bbmas-2017-with-over-the-top-medley-watch-w483310|title=Watch Nicki Minaj Open BBMAs With Star-Studded Medley |magazine= Rolling Stone| access-date=January 17, 2018|archive-date=June 27, 2018|archive-url=https://web.archive.org/web/20180627005916/https://www.rollingstone.com/music/news/nicki-minaj-opens-bbmas-2017-with-over-the-top-medley-watch-w483310|url-status=live}}</ref> Throughout the remainder of 2017, Minaj performed guest verses on several singles, including [[Migos]]' "[[MotorSport]]" and [[Yo Gotti]]'s "[[Rake It Up]]", both of which peaked in the top ten in the U.S., at numbers six and eight, respectively.<ref name="hot100_history">{{Cite magazine |title=Nicki Minaj (Chart History): ''Billboard'' Hot 100 |url=https://www.billboard.com/artist/nicki-minaj/chart-history/hsi/ |url-status=live |archive-url=https://web.archive.org/web/20211117181020/https://www.billboard.com/artist/nicki-minaj/chart-history/hsi/ |archive-date=November 17, 2021 |access-date=January 17, 2018 |magazine=Billboard }}</ref> She also featured on [[Katy Perry]]'s "[[Swish Swish]]", which was certified platinum in the U.S. and Canada.<ref>{{Cite news|url=https://www.riaa.com/riaa-presents-katy-perry-record-breaking-three-diamond-song-awards-d-c-stop-witness-tour/|title=RIAA Presents Katy Perry With Record=Breaking Three Diamond Song Awards During D.C. Stop of Witness: The Tour |date=September 26, 2017|publisher= Recording Industry Association of America |access-date= May 6, 2018|language=en-US|archive-date=August 22, 2023|archive-url=https://web.archive.org/web/20230822115906/https://www.riaa.com/riaa-presents-katy-perry-record-breaking-three-diamond-song-awards-d-c-stop-witness-tour/|url-status=live}}</ref> Minaj's recording contract with Young Money expired in 2017.<ref name="YM contract">{{cite web|url=https://www.complex.com/music/a/cmplxtara-mahadevan/birdman-drake-nicki-minaj-lil-wayne-2-billion-cash-money|title=Birdman Says Cash Money Made Over $2 Billion Just on Drake, Nicki Minaj and Lil Wayne|work=Complex|date=April 28, 2025|access-date=|last=Mahadevan|first=Tara|archive-url=https://web.archive.org/web/20250428235520/https://www.complex.com/music/a/cmplxtara-mahadevan/birdman-drake-nicki-minaj-lil-wayne-2-billion-cash-money|archive-date=April 28, 2025}}</ref>
=== 2018–2019: ''Queen'' ===
{{Further|Queen (Nicki Minaj album)}}
After a social media hiatus, Minaj released two singles from her fourth studio album ''[[Queen (Nicki Minaj album)|Queen]].'' She released "[[Chun-Li (song)|Chun-Li]]" and "[[Barbie Tingz]]" simultaneously on April 12, 2018, with both reaching number ten and number twenty-five in the US respectively.<ref name=":32">{{Cite magazine |title=Nicki Minaj's 'Chun-Li' Is Her First Solo Hot 100 Top 10 Since 'Anaconda' in 2014 |magazine=Billboard |url=https://www.billboard.com/pro/nicki-minaj-chun-li-anaconda-hot-100-top-10s/ |access-date=August 11, 2018 |archive-date=November 1, 2021 |archive-url=https://web.archive.org/web/20211101000527/https://www.billboard.com/articles/columns/chart-beat/8376546/nicki-minaj-chun-li-anaconda-hot-100-top-10s |url-status=live }}</ref> She performed "Chun-Li" on ''[[Saturday Night Live]]'' and the [[BET Awards 2018|2018 BET Awards]].<ref name=":2">{{cite magazine |title='SNL': Nicki Minaj Performs 'Chun-Li' and 'Poke it Out,' Appears in Unaired Sketch |magazine=Billboard |url=https://www.billboard.com/music/rb-hip-hop/snl-saturday-night-live-nicki-minaj-chun-li-playboi-carti-queens-tina-fey-8456836/ |access-date=May 22, 2018 |archive-date=June 12, 2018 |archive-url=https://web.archive.org/web/20180612141752/https://www.billboard.com/articles/columns/hip-hop/8456836/snl-saturday-night-live-nicki-minaj-chun-li-playboi-carti-queens-tina-fey |url-status=live }}</ref><ref>{{cite magazine |last=Reed |first=Ryan |date=June 25, 2018 |title=Nicki Minaj Performs Intense 'Chun Li,' 'Rich Sex' at BET Awards |url=https://www.rollingstone.com/music/news/nicki-minaj-chun-li-rich-sex-bet-awards-w521913 |magazine=Rolling Stone |access-date=June 26, 2018 |archive-date=June 26, 2018 |archive-url=https://web.archive.org/web/20180626164237/https://www.rollingstone.com/music/news/nicki-minaj-chun-li-rich-sex-bet-awards-w521913 |url-status=dead }}</ref> While "Chun Li" appears on the tracklist of the album, "Barbie Tingz" is only included on the [[Target Corporation|Target]] version of the album.<ref name=":04">{{Cite news |date=June 14, 2018 |title=Nicki Minaj Fans Are Bursting Into Rage After Discovering This One Thing Missing From Her Album.. |url=https://www.bet.com/article/31zcyw/nicki-minaj-fans-react-to-the-queen-tracklist |access-date=June 8, 2018 |website=[[BET]] |archive-date=August 6, 2018 |archive-url=https://web.archive.org/web/20180806210838/https://www.bet.com/music/2018/06/14/nicki-minaj-barbie-tingz-tracklist-queen.html/ |url-status=live }}</ref> On the red carpet of the 2018 [[Met Gala]], she announced the title of the album and the original release date.<ref>{{Cite magazine |title=Nicki Minaj Announces New Album 'Queen' at Met Gala |magazine=Billboard |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-new-album-queen-met-gala-8455023/ |access-date=May 8, 2018 |archive-date=May 8, 2018 |archive-url=https://web.archive.org/web/20180508132532/https://www.billboard.com/articles/columns/hip-hop/8455023/nicki-minaj-new-album-queen-met-gala |url-status=live }}</ref> "[[Rich Sex]]" featuring [[Lil Wayne]] was released on June 11, 2018, as the first promotional single from ''Queen''.<ref name=":8" /> The second single, "[[Bed (Nicki Minaj song)|Bed]]", featuring [[Ariana Grande]] was released on June 14, 2018, alongside the album pre-order, reaching number 42 on the Hot 100.<ref name=":8">{{cite web|url=https://pitchfork.com/news/listen-to-nicki-minaj-and-lil-waynes-new-song-rich-sex/|title=Listen to Nicki Minaj and Lil Wayne's New Song 'Rich Sex'|last=Strauss|first=Matthew|date=June 11, 2018|website=Pitchfork|access-date=August 19, 2018|archive-date=June 12, 2018|archive-url=https://web.archive.org/web/20180612073705/https://pitchfork.com/news/listen-to-nicki-minaj-and-lil-waynes-new-song-rich-sex/|url-status=live}}</ref><ref>{{cite web|url=https://www.instagram.com/p/Bj49Ca7hYUj/|title=Nicki Minaj on Instagram|via=Instagram|access-date=August 19, 2018|archive-date=August 26, 2022|archive-url=https://web.archive.org/web/20220826003111/https://www.instagram.com/p/Bj49Ca7hYUj/|url-status=live}}</ref>{{Primary source inline|date=July 2024}} Additionally, Minaj appeared as a featured artist on the song "[[Fefe (song)|Fefe]]" along with [[Murda Beatz]] and [[6ix9ine|Tekashi 6ix9ine]]. It debuted at number four in the US, marking Minaj's highest debut on the chart at the time, besting the sixth place start of "Bang Bang" in 2014.<ref name="US peak">{{cite magazine |last=Trust |first=Gary |date=August 6, 2018 |title=Drake Dominates Hot 100 for Fourth Week With 'In My Feelings,' DJ Khaled's All-Star 'No Brainer' Debuts at No. 5 |url=https://www.billboard.com/pro/drake-in-my-feelings-hot-100/ |magazine=Billboard |access-date=August 7, 2018 |archive-date=September 18, 2019 |archive-url=https://web.archive.org/web/20190918214259/https://www.billboard.com/articles/columns/chart-beat/8468763/drake-in-my-feelings-hot-100 |url-status=live }}</ref><ref name="bb">{{cite magazine|url=https://www.billboard.com/pro/drake-in-my-feelings-number-one-third-week/|title=Drake's 'In My Feelings' No. 1 For Third Week on Hot 100, Drake Passes Rihanna For Most Weeks at No. 1 This Decade|last=Trust|first=Gary|date=July 30, 2018|magazine=Billboard|access-date=July 31, 2018|archive-date=July 30, 2018|archive-url=https://web.archive.org/web/20180730214732/https://www.billboard.com/articles/columns/chart-beat/8467628/drake-in-my-feelings-number-one-third-week|url-status=live}}</ref> It later peaked at number three and was added to ''Queen'' in the middle of its first tracking week.<ref name="US peak" /><ref>{{cite magazine|url=https://www.billboard.com/articles/news/8470463/nicki-minaj-adds-6ix9ine-fefe-to-queen-tracklist|title=Nicki Minaj Adds 6ix9ine Collaboration 'Fefe' to 'Queen' Tracklist|date=August 15, 2018|magazine=Billboard|access-date=August 15, 2018|archive-date=August 20, 2018|archive-url=https://web.archive.org/web/20180820031714/https://www.billboard.com/articles/news/8470463/nicki-minaj-adds-6ix9ine-fefe-to-queen-tracklist|url-status=live}}</ref> The day before the album's release, Minaj launched her own [[Apple Music 1|Beats 1]] (now branded as Apple Music 1) radio show, ''Queen Radio''.<ref>{{cite news|url=http://www.papermag.com/nicki-minaj-queen-radio-2594042177.html|title=Nicki Minaj Launches 'Queen Radio'|last=Goldfine|first=Jael|date=August 8, 2018|work=[[Paper (magazine)|Paper]]|access-date=August 10, 2018|archive-date=August 15, 2021|archive-url=https://web.archive.org/web/20210815083216/https://www.papermag.com/nicki-minaj-queen-radio-2594042177.html|url-status=live}}</ref><ref>{{cite news|url=https://www.vibe.com/2018/08/nicki-minaj-announces-queen-radio-beats-1/|title=Nicki Minaj Announces Queen Radio On Beats 1|last=McKinney|first=Jessica|date=August 8, 2018|work=[[Vibe (magazine)|Vibe]]|access-date=August 10, 2018|archive-date=June 26, 2019|archive-url=https://web.archive.org/web/20190626212449/https://www.vibe.com/2018/08/nicki-minaj-announces-queen-radio-beats-1|url-status=live}}</ref> It became the most-listened radio show on [[Apple Music]] in 2019,<ref>{{Cite magazine |last=Devin |date=July 24, 2019 |title=Nicki Minaj's Queen Radio Sets Apple Music Record |url=https://www.rap-up.com/article/2019/07/24/nicki-minaj-queen-radio-sets-apple-music-record/ |access-date=September 17, 2024 |magazine=[[Rap-Up]] |archive-date=September 17, 2024 |archive-url=https://web.archive.org/web/20240917163451/https://www.rap-up.com/article/2019/07/24/nicki-minaj-queen-radio-sets-apple-music-record |url-status=live }}</ref> and ran for 17 episodes till February 2020.<ref name=":11">{{Cite web |last=Kearns |first=Sarah |date=March 9, 2022 |title=Nicki Minaj Revives 'Queen Radio' on Amazon's New App Amp |url=https://hypebeast.com/2022/3/nicki-minaj-queen-radio-amazon-amp-launch |access-date=September 17, 2024 |website=[[Hypebeast (company)|Hypebeast]] |archive-date=October 7, 2022 |archive-url=https://web.archive.org/web/20221007174426/https://hypebeast.com/2022/3/nicki-minaj-queen-radio-amazon-amp-launch |url-status=live }}</ref><ref>{{Cite magazine |last=Saponara |first=Michael |date=February 10, 2020 |title=Nicki Minaj Reveals Why She's Never Worked With Kendrick Lamar |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-queen-radio-episode-17-recap-kendrick-lamar-50-cent-karol-g-8550650/ |access-date=September 17, 2024 |magazine=Billboard |archive-date=September 17, 2024 |archive-url=https://web.archive.org/web/20240917163450/https://www.billboard.com/music/rb-hip-hop/nicki-minaj-queen-radio-episode-17-recap-kendrick-lamar-50-cent-karol-g-8550650/ |url-status=live }}</ref> Guests on the show included Ariana Grande, [[50 Cent]], and [[Chance the Rapper]], among others.<ref name=":11" />[[File:Nicki Minaj VMAs 2018 (2) (cropped).jpg|thumb|right|Minaj performing at the [[2018 MTV Video Music Awards]]|upright]]''Queen'' received multiple delays before finally being released on August 10, 2018.<ref>{{cite news |last=Garca |first=Anna |date=May 24, 2018 |title=Nicki Minaj Delays 'Queen' Album Release to August |work=[[Spin (magazine)|Spin]] |url=https://www.spin.com/2018/05/nicki-minaj-queen-release-date-delay-instagram-live/ |access-date=August 10, 2018 |archive-date=June 17, 2018 |archive-url=https://web.archive.org/web/20180617101950/https://www.spin.com/2018/05/nicki-minaj-queen-release-date-delay-instagram-live/ |url-status=live }}</ref><ref>{{Cite magazine |title=Nicki Minaj Pushes Back 'Queen' Album to August 17 |magazine=Billboard |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-pushes-back-queen-album-8468100/ |access-date=September 6, 2018 |archive-date=October 1, 2021 |archive-url=https://web.archive.org/web/20211001235021/https://www.billboard.com/articles/columns/hip-hop/8468100/nicki-minaj-pushes-back-queen-album |url-status=live }}</ref><ref name=":4">{{Cite news |last=Melas |first=Chloe |title=Nicki Minaj drops 'Queen' |publisher=CNN |url=https://www.cnn.com/2018/08/10/entertainment/nicki-minaj-queen-album/index.html |access-date=August 11, 2018 |archive-date=August 10, 2018 |archive-url=https://web.archive.org/web/20180810183814/https://www.cnn.com/2018/08/10/entertainment/nicki-minaj-queen-album/index.html |url-status=live }}</ref> It debuted at number two on the U.S. ''Billboard'' 200 with 185,000 [[album-equivalent unit]]s, of which 78,000 came from pure album sales.<ref>{{Cite magazine|url=https://www.billboard.com/pro/travis-scott-astroworld-no-1-second-week-billboard-200/|title=Travis Scott's 'Astroworld' Spends Second Week at No. 1 on Billboard 200 Albums Chart|date=August 19, 2018|magazine=Billboard|access-date=August 19, 2018|archive-date=August 20, 2018|archive-url=https://web.archive.org/web/20180820005920/https://www.billboard.com/articles/columns/chart-beat/8470929/travis-scott-astroworld-no-1-second-week-billboard-200|url-status=live}}</ref> It also debuted at number five in the UK and at number four in Australia, marking Minaj's highest debut in the latter country.<ref>{{cite web|url=https://www.ariacharts.com.au/news/2018/drake-returns-to-1-with-scorpion|title=Drake returns to #1 with Scorpion|publisher=[[Australian Recording Industry Association]]|access-date=August 19, 2018|archive-date=August 19, 2018|archive-url=https://web.archive.org/web/20180819051421/https://www.ariacharts.com.au/news/2018/drake-returns-to-1-with-scorpion|url-status=live}}</ref><ref>{{cite web|url=http://www.officialcharts.com/chart-news/super-troupers-mamma-mias-motion-picture-cast-recording-scores-fourth-week-at-albums-chart-top-spot__23674/|title=Super Troupers! Mamma Mia's Motion Picture Cast Recording scores fourth week at Albums Chart top spot|date=August 17, 2018|publisher=[[Official Charts Company]]|access-date=August 17, 2018|archive-date=August 17, 2018|archive-url=https://web.archive.org/web/20180817225556/http://www.officialcharts.com/chart-news/super-troupers-mamma-mias-motion-picture-cast-recording-scores-fourth-week-at-albums-chart-top-spot__23674/|url-status=live}}</ref> Minaj later expressed frustration and criticized several people including [[Travis Scott]], whose album ''[[Astroworld (album)|Astroworld]]'' claimed the top spot for a second week in a row, blocking ''Queen'' from the top spot. Minaj alleged that Travis Scott sold shirts, merchandise, and ticket passes for an unnannounced tour to boost his album sales.<ref name="billboard.com">{{cite magazine|title=Nicki Minaj Tells Ellen That She 'Wanted to Punch' Travis Scott After 'Queen' Failed to Go No. 1|magazine=Billboard|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-ellen-interview-travis-scott-8473415/|access-date=September 6, 2018|archive-date=September 30, 2021|archive-url=https://web.archive.org/web/20210930160443/https://www.billboard.com/articles/columns/hip-hop/8473415/nicki-minaj-ellen-interview-travis-scott|url-status=live}}</ref> This controversy and ''Queen''{{'s}} roll-out was documented by several news outlets and commentators.<ref>{{cite news|url=https://www.theatlantic.com/entertainment/archive/2018/08/nicki-minaj-queen-review/567499/|title=Nicki Minaj Guards a Shrinking Kingdom|last=Kornhaber|first=Spencer|date=August 15, 2018|work=The Atlantic|access-date=September 6, 2018|language=en-US|archive-date=August 15, 2018|archive-url=https://web.archive.org/web/20180815223323/https://www.theatlantic.com/entertainment/archive/2018/08/nicki-minaj-queen-review/567499/|url-status=live}}</ref><ref>{{cite news|url=https://www.nytimes.com/2018/08/20/arts/music/nicki-minaj-travis-scott-queen-astroworld.html|title=Nicki Minaj and Travis Scott Fumble Toward Yesterday's Prize: A No. 1 Album|work=[[The New York Times]]|date=August 20, 2018|access-date=June 9, 2020|language=en|url-access=registration|archive-date=July 13, 2023|archive-url=https://web.archive.org/web/20230713050006/https://www.nytimes.com/2018/08/20/arts/music/nicki-minaj-travis-scott-queen-astroworld.html|url-status=live}}</ref><ref>{{cite news|url=https://www.theringer.com/music/2018/8/20/17761826/nicki-minaj-queen-sales-billboard-charts-spotify-beats-1-travis-scott-astroworld|title=Nicki Minaj's 'Queen' May Not Survive Its Own Rollout|work=The Ringer|access-date=September 6, 2018|archive-date=July 13, 2023|archive-url=https://web.archive.org/web/20230713045649/https://www.theringer.com/music/2018/8/20/17761826/nicki-minaj-queen-sales-billboard-charts-spotify-beats-1-travis-scott-astroworld|url-status=live}}</ref> ''Queen'' received generally positive reviews, with some critics deriding the album's length and direction.<ref>{{cite web|title=Reviews for Queen by Nicki Minaj |url=http://www.metacritic.com/music/queen/nicki-minaj|url-status=live|archive-url=https://web.archive.org/web/20180905233813/https://www.metacritic.com/music/queen/nicki-minaj |archive-date=September 5, 2018|access-date=September 14, 2018|publisher=[[Metacritic]]}}</ref> The album was certified Platinum by the [[RIAA]] in January 2019, for moving over 1 million equivalent units.<ref>{{Cite web|url=https://www.riaa.com/gold-platinum/?tab_active=default-award&ar=nicki+minaj&ti=&lab=&genre=&format=&date_option=release&from=&to=&award=&type=&category=&adv=SEARCH#search_section|title=Gold & Platinum|website=[[Recording Industry Association of America]]|access-date=April 13, 2021|archive-date=October 1, 2021|archive-url=https://web.archive.org/web/20211001205433/https://www.riaa.com/gold-platinum/?tab_active=default-award&ar=nicki+minaj&ti=&lab=&genre=&format=&date_option=release&from=&to=&award=&type=&category=&adv=SEARCH#search_section|url-status=live}}</ref>
Minaj performed at the [[2018 MTV Video Music Awards]] where she also won her fourth MTV Video Music Award for the "Chun-Li" music video.<ref>{{Cite magazine |last=Berry |first=Peter A. |date=August 20, 2018 |title=Nicki Minaj's "Chun-Li" Wins Best Hip-Hop at 2018 MTV VMAs – XXL |language=en |magazine=XXL Mag |url=http://www.xxlmag.com/video/2018/08/nicki-minaj-chun-li-wins-best-hip-hop-2018-mtv-video-music-awards/ |access-date=August 23, 2018 |archive-date=August 1, 2019 |archive-url=https://web.archive.org/web/20190801114518/https://www.xxlmag.com/video/2018/08/nicki-minaj-chun-li-wins-best-hip-hop-2018-mtv-video-music-awards/ |url-status=live }}</ref> The same month, she featured on the remix of "[[Idol (BTS song)|Idol]]" by South Korean boy band [[BTS]]. It debuted and peaked at number eleven in the U.S. which was the group's second highest-charting song at the time.<ref>{{cite magazine|url=https://www.billboard.com/pro/bts-idol-nicki-minaj-hot-100-debut/|title=BTS' 'IDOL,' Featuring Nicki Minaj, Debuts at No. 11 on Billboard Hot 100|magazine=Billboard|access-date=September 6, 2018|archive-date=June 27, 2021|archive-url=https://web.archive.org/web/20210627190831/https://www.billboard.com/articles/columns/chart-beat/8473377/bts-idol-nicki-minaj-hot-100-debut|url-status=live}}</ref> She also featured on the song "[[Woman Like Me]]" by British girl group [[Little Mix]], which was released on October 12, 2018.<ref>{{cite magazine |title=Little Mix Announces New Single 'Woman Like Me,' Featuring Nicki Minaj |magazine=Billboard |url=https://www.billboard.com/music/pop/little-mix-woman-like-me-nicki-minaj-single-8477578/ |access-date=October 26, 2018 |archive-date=October 1, 2018 |archive-url=https://web.archive.org/web/20181001031050/https://www.billboard.com/articles/columns/pop/8477578/little-mix-woman-like-me-nicki-minaj-single |url-status=live }}</ref> A music video was shortly released after, with Minaj and Little Mix later performing the song together at the [[2018 MTV Europe Music Awards]].<ref>{{cite magazine|url=https://www.rollingstone.com/music/music-news/little-mix-nicki-minaj-woman-like-me-video-747562/|title=Watch Little Mix, Nicki Minaj Challenge Stereotypes in 'Woman Like Me' Video|last=Legaspi|first=Althea|date=October 26, 2018|magazine=Rolling Stone|access-date=October 26, 2018|language=en-US|archive-date=July 21, 2021|archive-url=https://web.archive.org/web/20210721072335/https://www.rollingstone.com/music/music-news/little-mix-nicki-minaj-woman-like-me-video-747562/|url-status=dead}}</ref> She was also featured on [[Dip (song)|"Dip"]] by American rapper [[Tyga]] which reached number sixty-three in the U.S. making her the first female artist to have 100 entries on the ''Billboard'' Hot 100 chart.<ref>{{cite magazine |last1=Trust |first1=Gary |title=Nicki Minaj Makes History as First Woman With 100 Appearances on Billboard Hot 100 |url=https://www.billboard.com/pro/nicki-minaj-first-woman-100-hits-hot-100/ |magazine=Billboard |access-date=November 20, 2018 |date=November 5, 2018 |archive-date=April 28, 2022 |archive-url=https://web.archive.org/web/20220428123608/https://www.billboard.com/pro/nicki-minaj-first-woman-100-hits-hot-100/ |url-status=live }}</ref> She later attended the year-end [[Billboard Women in Music|''Billboard'' Women in Music]] event, receiving the Game Changer Award for the accomplishment.<ref>{{cite magazine |date=November 15, 2019 |title=Alanis Morissette, Nicki Minaj, Brandi Carlile & Desiree Perez to Be Honored at Billboard's 2019 Women in Music |url=https://www.billboard.com/articles/news/awards/8543700/women-in-music-alanis-morissette-nicki-minaj-brandi-carlile-desiree-perez |access-date=November 15, 2019 |magazine=Billboard |archive-date=June 6, 2020 |archive-url=https://web.archive.org/web/20200606185555/https://www.billboard.com/articles/news/awards/8543700/women-in-music-alanis-morissette-nicki-minaj-brandi-carlile-desiree-perez |url-status=live }}</ref>
In September, Minaj co-headlined the annual [[Made in America Festival]] and suffered another wardrobe malfunction when the front of her outfit fell open.<ref>{{cite news|url=https://www.vulture.com/2018/09/made-in-america-festival-2018-best-and-worst-moments.html|title=The Best and Worst Moments From the 2018 Made in America Festival|last=Lockett|first=Dee|work=Vulture|access-date=September 6, 2018|language=en|archive-date=September 6, 2018|archive-url=https://web.archive.org/web/20180906004241/http://www.vulture.com/2018/09/made-in-america-festival-2018-best-and-worst-moments.html|url-status=live}}</ref> In November, she was invited to the DWP music festival in China but did not perform due to problems with the company hosting the event.<ref>{{Cite web|url=https://consequenceofsound.net/2018/11/nicki-minaj-scam-dwp-festival/|title=Nicki Minaj flew 6,000 miles to China for a counterfeit music festival|date=November 21, 2018|website=Consequence of Sound|language=en-US|access-date=April 24, 2019|archive-date=April 24, 2019|archive-url=https://web.archive.org/web/20190424212931/https://consequenceofsound.net/2018/11/nicki-minaj-scam-dwp-festival/|url-status=live}}</ref> The next year in April 2019, Minaj made a surprise guest appearance at the 2019 [[Coachella Valley Music and Arts Festival]] during [[Ariana Grande]]'s set where they performed their collaborations "[[Side to Side]]" and "[[Bang Bang (Jessie J, Ariana Grande and Nicki Minaj song)|Bang Bang]]". However, she experienced technical difficulties with her earpiece.<ref>{{cite web|url=https://variety.com/2019/music/news/sound-fails-nicki-minaj-and-ariana-grande-during-coachella-performance-1203189513/|title=Sound Fails Nicki Minaj and Ariana Grande During Coachella Performance|website=[[Variety (magazine)|Variety]]|first=Stuart|last=Oldham|date=April 15, 2019|access-date=April 15, 2019|archive-date=April 15, 2019|archive-url=https://web.archive.org/web/20190415084309/https://variety.com/2019/music/news/sound-fails-nicki-minaj-and-ariana-grande-during-coachella-performance-1203189513/|url-status=live}}</ref> Later that month, Minaj parted ways with her longtime management team after a mutual agreement, with American entertainment executive [[Irving Azoff]] serving as her new manager.<ref>{{Cite magazine |last=Aswad |first=Jem |date=April 18, 2019 |title=Nicki Minaj Parts Ways With Longtime Managers |url=https://variety.com/2019/biz/news/nicki-minaj-parts-ways-with-longtime-managers-exclusive-1203192785/ |access-date=March 8, 2022 |website=[[Variety (magazine)|Variety]] |language=en-US |archive-date=March 8, 2022 |archive-url=https://web.archive.org/web/20220308205751/https://variety.com/2019/biz/news/nicki-minaj-parts-ways-with-longtime-managers-exclusive-1203192785/ |url-status=live }}</ref><ref>{{Cite magazine |last=Willman |first=Chris |date=June 24, 2019 |title=Irving Azoff Assures Nicki Minaj That Travis Scott Doesn't Hate Her: 'It's All Show Biz' |url=https://variety.com/2019/music/news/nicki-minaj-irving-azoff-travis-scott-1203252076/ |access-date=March 8, 2022 |website=[[Variety (magazine)|Variety]] |language=en-US |archive-date=March 8, 2022 |archive-url=https://web.archive.org/web/20220308205750/https://variety.com/2019/music/news/nicki-minaj-irving-azoff-travis-scott-1203252076/ |url-status=live }}</ref>
In June 2019, Minaj released a standalone single titled "[[Megatron (song)|Megatron]]" along with an accompanying music video. In the U.S., the song debuted at number twenty. A month later, she provided information on her upcoming fifth studio album, appearing on ''[[The Tonight Show Starring Jimmy Fallon]]'', stating that "there's definitely a new album, of course."<ref>{{Cite magazine |date=June 28, 2019 |title=Nicki Minaj Takes Jimmy Fallon to Red Lobster, Plays 'Wheel of Freestyle' and Talks New Album: Watch |url=https://www.billboard.com/music/music-news/nicki-minaj-jimmy-fallon-red-lobster-new-album-freestyle-8517994/ |access-date=March 8, 2022 |magazine=[[Billboard (magazine)|Billboard]] |language=en-US |archive-date=March 8, 2022 |archive-url=https://web.archive.org/web/20220308205751/https://www.billboard.com/music/music-news/nicki-minaj-jimmy-fallon-red-lobster-new-album-freestyle-8517994/ |url-status=live }}</ref> She featured on the song "[[Hot Girl Summer]]" alongside fellow female rapper [[Megan Thee Stallion]] in August 2019, which reached number eleven in the U.S.<ref>{{Cite magazine |last=Anderson |first=Trevor |date=August 21, 2019 |title=Megan Thee Stallion, Nicki Minaj & Ty Dolla $ign's 'Hot Girl Summer' Debuts in Top 10 on Hot R&B/Hip-Hop Songs |url=https://www.billboard.com/pro/megan-thee-stallion-hot-girl-summer-top-10-hot-rb-hip-hop-songs-chart/ |access-date=March 8, 2022 |magazine=[[Billboard (magazine)|Billboard]] |language=en-US |archive-date=March 8, 2022 |archive-url=https://web.archive.org/web/20220308205750/https://www.billboard.com/pro/megan-thee-stallion-hot-girl-summer-top-10-hot-rb-hip-hop-songs-chart/ |url-status=live }}</ref> Minaj had a voice role as Pinky in ''[[The Angry Birds Movie 2]]'', which was released in August 2019.<ref>{{cite magazine|url=https://variety.com/news/nicki-minaj-angry-birds-movie-2/|title=Nicki Minaj Joins 'Angry Birds Movie 2' Voice Cast (EXCLUSIVE)|website=[[Variety (magazine)|Variety]]|first=Dave|last=McNary|date=December 24, 2018|access-date=December 24, 2018|archive-date=December 1, 2020|archive-url=https://web.archive.org/web/20201201232854/https://variety.com/2018/film/news/nicki-minaj-angry-birds-movie-2-1203095526/|url-status=live}}</ref> In November, Minaj appeared on the ''[[Charlie's Angels: Original Motion Picture Soundtrack]]'' on the song "Bad To You", along with [[Ariana Grande]] and [[Normani]], marking her sixth collaboration with Grande.<ref>{{Cite magazine |last=Zemler |first=Emily |date=October 11, 2019 |title=Ariana Grande to Collaborate With Normani and Nicki Minaj on 'Charlie's Angels' Soundtrack |url=https://www.rollingstone.com/music/music-news/ariana-grande-normani-nicki-minaj-charlies-angels-soundtrack-897729/ |access-date=March 8, 2022 |magazine=Rolling Stone |language=en-US |archive-date=March 8, 2022 |archive-url=https://web.archive.org/web/20220308212622/https://www.rollingstone.com/music/music-news/ariana-grande-normani-nicki-minaj-charlies-angels-soundtrack-897729/ |url-status=live }}</ref> Minaj also collaborated with Colombian singer [[Karol G]] on the song "[[Tusa (song)|Tusa]]", which was released to streaming platforms on November 7. The song reached forty-two in the U.S. and reached the top of many other charts including the "[[Hot Latin Songs]]" chart, which made it the first song with two lead female artists to debut in such position.<ref name="ReferenceB">{{Cite magazine|date=November 20, 2019|title=Karol G & Nicki Minaj's 'Tusa' Becomes First Song by Two Lead Female Artists to Debut at No. 1 on Hot Latin Songs|url=https://www.billboard.com/pro/karol-g-nicki-minaj-debut-number-one-latin-songs/|magazine=Billboard|access-date=March 28, 2021|archive-date=November 26, 2021|archive-url=https://web.archive.org/web/20211126162835/https://www.billboard.com/pro/karol-g-nicki-minaj-debut-number-one-latin-songs/|url-status=live}}</ref> "Tusa" later became the longest-running number one single in Argentina, spending five months on the top of the chart.<ref name="ArgentinaHot100">{{cite web |last=Armendariz |first=Josefina |date=June 11, 2020 |title=Billboard Argentina Hot 100: Anuel AA y Bad Bunny lograron el Hot Shot Debut |url=https://billboard.com.ar/billboard-argentina-hot-100-anuel-aa-y-bad-bunny-lograron-el-hot-shot-debut/ |url-status=live |archive-url=https://web.archive.org/web/20200611202558/https://billboard.com.ar/billboard-argentina-hot-100-anuel-aa-y-bad-bunny-lograron-el-hot-shot-debut/ |archive-date=June 11, 2020 |access-date=June 11, 2020 |work=[[Billboard Argentina]] |language=es}}</ref>
=== 2020–2021: ''Beam Me Up Scotty'' re-release ===
{{Further|Beam Me Up Scotty (mixtape)}}
In January 2020, Minaj was featured on "[[Nice to Meet Ya (Meghan Trainor song)|Nice to Meet Ya]]" by American singer-songwriter [[Meghan Trainor]].<ref>{{cite magazine |last=Saponara |first=Michael |date=January 31, 2020 |title=Meghan Trainor Links Up With Nicki Minaj at the Office for Colorful 'Nice to Meet Ya' Video |url=https://www.billboard.com/music/rb-hip-hop/meghan-trainor-nicki-minaj-nice-to-meet-ya-video-8549784/ |magazine=[[Billboard (magazine)|Billboard]] |access-date=February 6, 2020 |archive-date=February 7, 2020 |archive-url=https://web.archive.org/web/20200207034248/https://www.billboard.com/articles/columns/hip-hop/8549784/meghan-trainor-nicki-minaj-nice-to-meet-ya-video |url-status=live }}</ref><ref>{{Cite magazine |last=Mamo |first=Heran |date=February 13, 2020 |title=Here Are the Lyrics to Meghan Trainor's 'Nice to Meet Ya,' Feat. Nicki Minaj |url=https://www.billboard.com/music/lyrics/meghan-trainor-nice-to-meet-ya-lyrics-nicki-minaj-8550893/ |access-date=August 13, 2022 |magazine=[[Billboard (magazine)|Billboard]] |language=en-US |archive-date=July 13, 2023 |archive-url=https://web.archive.org/web/20230713045804/https://www.billboard.com/music/lyrics/meghan-trainor-nice-to-meet-ya-lyrics-nicki-minaj-8550893/ |url-status=live }}</ref> In February, she released a standalone song called "[[Yikes (Nicki Minaj song)|Yikes]]" as a promotional single.<ref>{{cite magazine |last=Saponara |first=Michael |date=February 4, 2020 |title=Nicki Minaj Slammed For Rosa Parks Reference in Preview of New Song 'Yikes' |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-previews-yikes-8550084/ |magazine=[[Billboard (magazine)|Billboard]] |access-date=February 6, 2020 |archive-date=June 17, 2020 |archive-url=https://web.archive.org/web/20200617144939/https://www.billboard.com/articles/columns/hip-hop/8550084/nicki-minaj-previews-yikes |url-status=live }}</ref><ref>{{Cite magazine |last=Kaufman |first=Gil |date=February 7, 2020 |title=In Case You Forgot, Nicki Minaj Proclaims Herself Queen on Fiery 'Yikes': Listen |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-yikes-8550401/ |access-date=August 12, 2022 |magazine=[[Billboard (magazine)|Billboard]] |language=en-US |archive-date=August 13, 2022 |archive-url=https://web.archive.org/web/20220813222843/https://www.billboard.com/music/rb-hip-hop/nicki-minaj-yikes-8550401/ |url-status=live }}</ref> By the end of that month, Minaj appeared as a guest judge on the [[RuPaul's Drag Race (season 12)|twelfth season]]'s premiere of the reality television show ''[[RuPaul's Drag Race]]''.<ref>{{Cite magazine |last=Nolfi |first=Joey |date=February 25, 2020 |title=Nicki Minaj comes for RuPaul's throne in Drag Race premiere clip |url=https://ew.com/tv/2020/02/25/nicki-minaj-rupauls-drag-race-clip/ |magazine=[[Entertainment Weekly]] |access-date=February 28, 2022 |archive-date=February 27, 2022 |archive-url=https://web.archive.org/web/20220227235056/https://ew.com/tv/2020/02/25/nicki-minaj-rupauls-drag-race-clip/ |url-status=live }}</ref><ref>{{Cite news |last=Griffiths |first=George |date=February 26, 2020 |title=Nicki Minaj replaces RuPaul on Drag Race season 12 runway |work=[[Metro (British newspaper)|Metro]] |url=https://metro.co.uk/2020/02/26/nicki-minaj-sissies-walk-replaces-rupaul-runway-drag-race-season-12-12304406/ |access-date=August 12, 2022 |archive-date=August 13, 2022 |archive-url=https://web.archive.org/web/20220813222754/https://metro.co.uk/2020/02/26/nicki-minaj-sissies-walk-replaces-rupaul-runway-drag-race-season-12-12304406/ |url-status=live }}</ref> In May, fellow rapper [[Doja Cat]] announced a collaboration with Minaj on two remixes of her song "[[Say So]]". The remix later debuted at number one on the U.S. [[Billboard Hot 100|''Billboard'' Hot 100]], becoming both Minaj and Doja Cat's first single to reach number one in the U.S. It became the first all-female collaboration that had reached number one in the U.S. since "[[Fancy (Iggy Azalea song)|Fancy]]" by Australian rapper [[Iggy Azalea]] featuring English singer [[Charli XCX]] did it in 2014.<ref name="bbhot100-peak12">{{Cite magazine|title=Doja Cat's 'Say So,' Featuring Nicki Minaj, Tops Billboard Hot 100, Becoming Their First No. 1 Each|url=https://www.billboard.com/articles/business/chart-beat/9375563/doja-cat-nicki-minaj-say-so-number-one|last=Trust|first=Gary|date=May 11, 2020|magazine=Billboard|access-date=May 11, 2020|archive-date=May 15, 2020|archive-url=https://web.archive.org/web/20200515135732/https://www.billboard.com/articles/business/chart-beat/9375563/doja-cat-nicki-minaj-say-so-number-one|url-status=live}}</ref><ref>{{Cite magazine |date=May 12, 2020 |title=Five Burning Questions: Doja Cat & Nicki Minaj's 'Say So' |url=http://www.billboard.com/articles/business/chart-beat/9376437/doja-cat-nicki-minaj-say-so-five-burning-questions |magazine=[[Billboard (magazine)|Billboard]] |access-date=May 14, 2020 |quote="...and the first collaboration between two female rappers to reach the chart's apex." |archive-date=September 27, 2020 |archive-url=https://web.archive.org/web/20200927195507/https://www.billboard.com/articles/business/chart-beat/9376437/doja-cat-nicki-minaj-say-so-five-burning-questions |url-status=live }}</ref><ref>{{Cite magazine |last=Robinson |first=Joshua |date=December 15, 2021 |title=Doja Cat & Nicki Minaj's "Say So" Remix Earns Them A New Guinness World Record |url=https://www.hotnewhiphop.com/doja-cat-and-nicki-minajs-say-so-remix-earns-them-a-new-guinness-world-record-news.144666.html |magazine=[[HotNewHipHop]] |language=en |access-date=March 26, 2022 |quote=Over a year later, that achievement has now been recorded in the history books, as the Guinness World Records have officially recognized the "Say So" remix as the first song from a female rap duo to become a Hot 100 chart-topper. |archive-date=May 6, 2022 |archive-url=https://web.archive.org/web/20220506174058/https://www.hotnewhiphop.com/doja-cat-and-nicki-minajs-say-so-remix-earns-them-a-new-guinness-world-record-news.144666.html |url-status=live }}</ref> A month later, she released a collaboration with [[6ix9ine]] called "[[Trollz (song)|Trollz]]", which debuted at number one in the U.S. becoming her second number one single.<ref>{{cite magazine|last=Trust|first=Gary|date=June 22, 2020|title=6ix9ine & Nicki Minaj's 'Trollz' Launches at No. 1 on Billboard Hot 100, Lil Baby's 'The Bigger Picture' Debuts at No. 3|url=https://www.billboard.com/articles/business/chart-beat/9406723/guess-guess|access-date=June 22, 2020|magazine=[[Billboard (magazine)|Billboard]]|archive-date=June 22, 2020|archive-url=https://web.archive.org/web/20200622234842/https://www.billboard.com/articles/business/chart-beat/9406723/guess-guess|url-status=live}}</ref> This made her the first female rapper to debut at number one on the ''Billboard'' Hot 100 since [[Lauryn Hill]] did so in 1998 with "[[Doo Wop (That Thing)]]".<ref name=":9">{{Cite web|date=June 22, 2020|title=Nicki Minaj Reacts to Going Number One in 2020 — Again|url=https://www.papermag.com/nicki-minaj-number-1-trollz-2646233182.html|access-date=September 20, 2021|website=PAPER|language=en|archive-date=October 2, 2021|archive-url=https://web.archive.org/web/20211002081240/https://www.papermag.com/nicki-minaj-number-1-trollz-2646233182.html|url-status=live}}</ref>
Minaj featured on singer [[Ty Dolla Sign]]'s track "[[Expensive (Ty Dolla Sign song)|Expensive]]" in August 2020.<ref>{{Cite magazine |last=Triscari |first=Caleb |date=August 28, 2020 |title=Ty Dolla $ign Announces New Single 'Expensive', Featuring Nicki Minaj |url=https://www.nme.com/news/music/ty-dolla-ign-announces-new-single-expensive-featuring-nicki-minaj-2737725 |magazine=[[NME]] |access-date=August 28, 2020 |archive-date=September 5, 2020 |archive-url=https://web.archive.org/web/20200905064722/https://www.nme.com/news/music/ty-dolla-ign-announces-new-single-expensive-featuring-nicki-minaj-2737725 |url-status=live }}</ref> She continued to appear as a featured artist on several songs in late 2020<ref>{{Cite magazine|url=https://www.billboard.com/articles/news/dance/9447449/major-lazer-mr-eazi-k4mo-nicki-minaj-oh-my-gawd|title=Major Lazer & Mr Eazi Call on K4mo & Nicki Minaj for 'Oh My Gawd'|last=Aniftos|first=Rania|magazine=[[Billboard (magazine)|Billboard]]|access-date=September 11, 2020|archive-date=September 11, 2020|archive-url=https://web.archive.org/web/20200911005148/https://www.billboard.com/articles/news/dance/9447449/major-lazer-mr-eazi-k4mo-nicki-minaj-oh-my-gawd|url-status=live}}</ref><ref>{{Cite magazine|url=https://www.complex.com/music/2020/10/nicki-minaj-sada-baby-whole-lotta-choppas-remix|title=Nicki Minaj Links With Sada Baby for 'Whole Lotta Choppas' Remix|access-date=October 16, 2020|website=Complex|archive-date=October 17, 2020|archive-url=https://web.archive.org/web/20201017161643/https://www.complex.com/music/2020/10/nicki-minaj-sada-baby-whole-lotta-choppas-remix|url-status=live}}</ref> and released "[[What That Speed Bout!?]]" with producer [[Mike Will Made It]] and rapper [[YoungBoy Never Broke Again]] on November 6.<ref>{{Cite magazine|url=https://pitchfork.com/news/nicki-minaj-and-youngboy-never-broke-again-join-mike-will-made-it-on-new-song-listen/|title=Nicki Minaj and YoungBoy Never Broke Again Join Mike WiLL Made-It on New Song: Listen|access-date=November 7, 2020|website=Pitchfork|date=November 6, 2020|archive-date=November 6, 2020|archive-url=https://web.archive.org/web/20201106153655/https://pitchfork.com/news/nicki-minaj-and-youngboy-never-broke-again-join-mike-will-made-it-on-new-song-listen/|url-status=live}}</ref> She was the most-streamed female rapper of the year on [[Spotify]].<ref>{{Cite web|date=December 3, 2020|title=Megan Thee Stallion, Cardi B, Nicki Minaj and More – How Female Rappers Dominated in 2020|url=https://www.wkyc.com/article/entertainment/entertainment-tonight/megan-thee-stallion-cardi-b-nicki-minaj-and-more-how-female-rappers-dominated-in-2020/603-f3a36ea1-839f-45f1-9c19-bd526d52dc94|access-date=September 5, 2021|publisher=[[WKYC]]|language=en-US}} "The Queens rapper, who reigns as Spotify's most streamed female rapper for 2020, celebrated the 10-year anniversary of her ''Pink Friday'' debut last month."</ref> On the 10th anniversary of her debut album ''[[Pink Friday]]'' (2010), Minaj announced a six-part [[docuseries]] about her, produced by [[Bron Studios]] and was originally said to premiere on [[HBO Max]].<ref>{{Cite magazine |last=Strauss |first=Matthew |date=November 20, 2020 |title=Nicki Minaj Announces New HBO Max Documentary Series |url=https://pitchfork.com/news/nicki-minaj-announces-new-hbo-max-documentary-series/ |magazine=[[Pitchfork (magazine)|Pitchfork]] |language=en-us |access-date=December 14, 2020 |archive-date=December 6, 2020 |archive-url=https://web.archive.org/web/20201206094126/https://pitchfork.com/news/nicki-minaj-announces-new-hbo-max-documentary-series/ |url-status=live }}</ref><ref>{{Cite magazine |last=Kreps |first=Daniel |date=November 20, 2020 |title=Nicki Minaj Announces 'Raw, Unfiltered' HBO Max Docuseries |url=https://www.rollingstone.com/music/music-news/nicki-minaj-hbo-max-docuseries-1093521/ |access-date=August 13, 2022 |magazine=[[Rolling Stone]] |archive-date=August 13, 2022 |archive-url=https://web.archive.org/web/20220813222842/https://www.rollingstone.com/music/music-news/nicki-minaj-hbo-max-docuseries-1093521/ |url-status=live }}</ref> In 2022, the streaming platform confirmed it dropped the still-unreleased project.<ref name=":15">{{Cite web |last=Alessandrini |first=Kyra |date=August 5, 2022 |title=Nicki Minaj Shares First Trailer For Six-Part Documentary Series |url=https://girlsunited.essence.com/article/nicki-minaj-first-trailer-documentary-series/ |access-date=August 13, 2022 |website=[[Essence (magazine)|Essence]] |language=en-US |archive-date=August 13, 2022 |archive-url=https://web.archive.org/web/20220813222754/https://girlsunited.essence.com/article/nicki-minaj-first-trailer-documentary-series/ |url-status=live }}</ref><ref name=":16">{{Cite web |last=Corrine |first=Amber |date=August 1, 2022 |title=Nicki Minaj Drops Trailer For Intimate 'Nicki' Documentary Series |url=https://www.vibe.com/news/movies-tv/nicki-minaj-documentary-series-trailer-1234682014/ |access-date=August 13, 2022 |website=[[Vibe (magazine)|Vibe]] |language=en-US |archive-date=August 12, 2022 |archive-url=https://web.archive.org/web/20220812025126/https://www.vibe.com/news/movies-tv/nicki-minaj-documentary-series-trailer-1234682014/ |url-status=live }}</ref>
In May 2021, Minaj released a reissue of her mixtape ''[[Beam Me Up Scotty (mixtape)|Beam Me Up Scotty]]'' (2009), including new songs previously unavailable on streaming services. The reissue debuted at number two on the U.S. [[Billboard 200|''Billboard'' 200]], which gave it the highest debut for a female rap mixtape in the U.S.<ref name=":10">{{Cite magazine |last=Caulfield |first=Keith |date=May 23, 2021 |title=J. Cole Collects Sixth No. 1 Album on Billboard 200 Chart With 'The Off-Season' |url=https://www.billboard.com/articles/news/9576787/j-cole-the-off-season-billboard-200-chart-number-one |magazine=[[Billboard (magazine)|Billboard]] |access-date=May 24, 2021 |quote=Nick Minaj's Beam Me Up Scotty mixtape debuts at No. 2 on the Billboard 200 with 80,000 equivalent album units earned. Of that sum, SEA units comprise 63,000 (equaling 85.57 million on-demand streams of the album's tracks), album sales comprise 11,000 and TEA units comprise 6,000. |archive-date=May 23, 2021 |archive-url=https://web.archive.org/web/20210523232301/https://www.billboard.com/articles/news/9576787/j-cole-the-off-season-billboard-200-chart-number-one/ |url-status=live }}</ref><ref>{{Cite web |last=Star |first=Regina |date=June 13, 2021 |title=Nicki Minaj Teases Fifth Album, Hints Its Title Is 'Officially Locked In' |url=https://www.iheart.com/content/2021-06-12-nicki-minaj-teases-fifth-album-hints-its-title-is-locked-in/ |access-date=December 13, 2021 |website=[[iHeartRadio|iHeart]] |language=en |quote=The reissue pulled off some impressive feats in its waking, having debuted at No. 2 on the Billboard 200, becoming the highest-debuting female rap mixtape in the chart's history. |archive-date=October 21, 2021 |archive-url=https://web.archive.org/web/20211021043150/https://www.iheart.com/content/2021-06-12-nicki-minaj-teases-fifth-album-hints-its-title-is-locked-in/ |url-status=live }}</ref> A song from the reissue, titled "[[Seeing Green]]" featuring fellow [[Young Money Entertainment|''Young Money'']] rappers [[Drake (musician)|Drake]] and [[Lil Wayne]] reached number twelve in the U.S. In July, Minaj collaborated with fellow rapper [[Bia (rapper)|Bia]] on the remix of her song ''[[Whole Lotta Money]]''. In September, Minaj collaborated with English singer-songwriter [[Elton John]]'s fifth collaboration album ''[[The Lockdown Sessions (Elton John album)|The Lockdown Sessions]]'' (2021). She appeared on the song "Always Love You" with John and rapper [[Young Thug]].<ref>{{Cite magazine|title=Elton John Teams Up With Lil Nas X, Dua Lipa & More for New Collaborations Album|url=https://www.billboard.com/articles/news/pride/9623292/elton-john-lockdown-sessions-album-announcement/|access-date=September 1, 2021|magazine=Billboard|language=en|archive-date=September 1, 2021|archive-url=https://web.archive.org/web/20210901165018/https://www.billboard.com/articles/news/pride/9623292/elton-john-lockdown-sessions-album-announcement/|url-status=live}}</ref> Later that month, she split with her previous manager [[Irving Azoff]] and is now being managed by [[Wassim Slaiby]], also known by his management company SALXCO, who is best known for managing [[The Weeknd]] and [[Doja Cat]].<ref>{{Cite magazine |last=Halperin |first=Shirley |date=September 14, 2021 |title=Nicki Minaj No Longer Managed by Irving Azoff; In Talks With SALXCO, Home to the Weeknd and Doja Cat (EXCLUSIVE) |url=https://variety.com/2021/music/news/nicki-minaj-irving-azoff-split-manager-salxco-1235040998/ |magazine=[[Variety (magazine)|Variety]] |language=en-US |access-date=September 16, 2021 |archive-date=September 16, 2021 |archive-url=https://web.archive.org/web/20210916033707/https://variety.com/2021/music/news/nicki-minaj-irving-azoff-split-manager-salxco-1235040998/ |url-status=live }}</ref> English singer [[Jesy Nelson]] released her solo debut, called "[[Boyz (Jesy Nelson song)|Boyz]]", which Minaj features on. The song peaked at number four in the UK and number sixteen in Ireland.<ref>{{Cite web |last=Ainsley |first=Helen |date=October 15, 2021 |title=Elton John & Dua Lipa's Cold Heart (Pnau Remix) climbs to Number 1 |url=https://www.officialcharts.com/chart-news/elton-john-and-dua-lipa-s-cold-heart-pnau-remix-claims-number-1-on-the-official-singles-chart__34232/ |access-date=October 15, 2021 |website=[[Official Charts Company]] |language=en |archive-date=February 11, 2023 |archive-url=https://web.archive.org/web/20230211141308/https://www.officialcharts.com/chart-news/elton-john-and-dua-lipa-s-cold-heart-pnau-remix-claims-number-1-on-the-official-singles-chart__34232/ |url-status=live }}</ref><ref>{{Cite web |title=Official Irish Singles Chart Top 50 {{!}} Official Charts Company |url=https://www.officialcharts.com/charts/irish-singles-chart/ |url-status=live |archive-url=https://web.archive.org/web/20211016034608/https://www.officialcharts.com/charts/irish-singles-chart/ |archive-date=October 16, 2021 |access-date=October 16, 2021 |website=[[Official Charts Company]] |language=en}}</ref> In October 2021, Minaj appeared as a surprise host for the [[The Real Housewives of Potomac (season 6)|season six]] reunion of the reality television show ''[[The Real Housewives of Potomac]]''.<ref>{{Cite news |last=Crist |first=Allison |date=October 29, 2021 |title=Nicki Minaj Steals the Show in The Real Housewives of Potomac Reunion Trailer |website=[[E! News]] |url=https://www.eonline.com/news/1307770/nicki-minaj-steals-the-show-in-the-real-housewives-of-potomac-reunion-trailer |access-date=November 1, 2021 |archive-date=November 1, 2021 |archive-url=https://web.archive.org/web/20211101184147/https://www.eonline.com/news/1307770/nicki-minaj-steals-the-show-in-the-real-housewives-of-potomac-reunion-trailer |url-status=live }}</ref> In November 2021, her 2011 ''[[Pink Friday]]'' single "Super Bass" received a Diamond certification by the [[Recording Industry Association of America|RIAA]], making her the second solo female rapper to receive a diamond certification.<ref name=":14">{{Cite magazine |last=Bowenbank |first=Starr |date=November 10, 2021 |title=Nicki Minaj Celebrates 'Super Bass' Being Certified Diamond by RIAA After 'Over a Decade of Support' |url=https://www.billboard.com/music/music-news/nicki-minaj-super-bass-riaa-diamond-certification-9658023/ |magazine=[[Billboard (magazine)|Billboard]] |language=en |access-date=November 12, 2021 |archive-date=November 12, 2021 |archive-url=https://web.archive.org/web/20211112030556/https://www.billboard.com/articles/columns/hip-hop/9658023/nicki-minaj-super-bass-riaa-diamond-certification |url-status=live }}</ref> Minaj won the [[MTV Europe Music Award for Best Hip Hop|Best Hip Hop]] award in the [[MTV Europe Music Awards|MTV Europe Music Awards 2021]], becoming the sixth time she has won this award.<ref>{{Cite news |date=November 15, 2021 |title=Ed Sheeran wins best artist as MTV Europe Music Awards 2021 returns to live format |language=en |work=[[Reuters]] |url=https://www.reuters.com/lifestyle/ed-sheeran-wins-best-artist-mtv-europe-music-awards-returns-live-format-2021-11-14/ |access-date=November 15, 2021 |quote=Nicki Minaj earned a top award for best hip-hop, Olivia Rodrigo was awarded best push, Maluma won best Latin, and YUNGBLUD snagged the award for best alternative. |archive-date=August 13, 2022 |archive-url=https://web.archive.org/web/20220813184622/https://www.reuters.com/lifestyle/ed-sheeran-wins-best-artist-mtv-europe-music-awards-returns-live-format-2021-11-14/ |url-status=live }}</ref>
=== 2022–present: ''Pink Friday 2'' ===
{{Further|Pink Friday 2}}
After a social media hiatus, Minaj released a song collaboration with rapper [[Lil Baby]] titled "[[Do We Have a Problem?]]" on February 4, 2022.<ref>{{Cite magazine |last=Legaspi |first=Althea |date=February 4, 2022 |title=Nicki Minaj Is a Double-Crossing Agent in 'Do We Have a Problem?' Video |url=https://www.rollingstone.com/music/music-news/nicki-minaj-lil-baby-do-we-have-a-problem-song-video-1293615/ |access-date=August 12, 2022 |magazine=[[Rolling Stone]] |archive-date=August 25, 2022 |archive-url=https://web.archive.org/web/20220825225918/https://www.rollingstone.com/music/music-news/nicki-minaj-lil-baby-do-we-have-a-problem-song-video-1293615/ |url-status=live}}</ref><ref name="etonline_dwhap">{{Cite web |last=Gawley |first=Paige |date=February 4, 2022 |title=Nicki Minaj Drops 'Do We Have a Problem' Song and Music Video With Lil Baby |url=https://www.etonline.com/nicki-minaj-drops-do-we-have-a-problem-song-and-music-video-with-lil-baby-178846 |access-date=August 13, 2022 |work=[[Entertainment Tonight]] |archive-date=August 25, 2022 |archive-url=https://web.archive.org/web/20220825225836/https://www.etonline.com/nicki-minaj-drops-do-we-have-a-problem-song-and-music-video-with-lil-baby-178846 |url-status=live}}</ref> It debuted at number two on the US ''Billboard'' Hot 100, marking Minaj's 20th top-ten.<ref>{{cite magazine|url=https://www.billboard.com/music/chart-beat/we-dont-talk-about-bruno-encanto-number-one-third-week-1235031295/|title='We Don't Talk About Bruno,' From 'Encanto,' Rules Billboard Hot 100 For Third Week|last=Trust|first=Gary|date=February 14, 2022|magazine=Billboard|access-date=September 17, 2024|archive-date=February 14, 2022|archive-url=https://web.archive.org/web/20220214231836/https://www.billboard.com/music/chart-beat/we-dont-talk-about-bruno-encanto-number-one-third-week-1235031295/|url-status=live}}</ref> On the [[Billboard Global 200|''Billboard'' Global 200]], it debuted at number seven, earning Minaj her first top-ten on the chart.<ref>{{cite magazine|last=Trust|first=Gary|url=https://www.billboard.com/music/chart-beat/we-dont-talk-about-bruno-abcdefu-hold-atop-global-charts-1235031333/|title='We Don't Talk About Bruno' & 'abcdefu' Hold Atop Billboard Global Charts|magazine=Billboard|date=February 14, 2022|access-date=September 17, 2024}}</ref> Another Lil Baby collaboration titled "[[Bussin (Nicki Minaj and Lil Baby song)|Bussin]]" was released a week later, on February 11.<ref>{{Cite magazine |last=Legaspi |first=Althea |date=February 11, 2022 |title=Nicki Minaj and Lil Baby Reteam for 'Bussin' |url=https://www.rollingstone.com/music/music-news/nicki-minaj-lil-baby-bussin-1297742/ |access-date=August 13, 2022 |magazine=[[Rolling Stone]] |archive-date=August 25, 2022 |archive-url=https://web.archive.org/web/20220825231804/https://www.rollingstone.com/music/music-news/nicki-minaj-lil-baby-bussin-1297742/ |url-status=live}}</ref><ref>{{cite magazine |last=Rose |first=Jordan |date=February 11, 2022 |title=Nicki Minaj and Lil Baby Link Up Again for New Song "Bussin" |url=https://www.complex.com/music/nicki-minaj-lil-baby-bussin |magazine=[[Complex Networks|Complex]] |access-date=February 11, 2022 |archive-date=February 11, 2022 |archive-url=https://web.archive.org/web/20220211051034/https://www.complex.com/music/nicki-minaj-lil-baby-bussin |url-status=live}}</ref> In an [[Apple Music]] interview with DJ [[Zane Lowe]], Minaj said her forthcoming fifth studio album was "coming very soon".<ref>{{cite magazine |last=Mahadevan |first=Tara C. |date=February 4, 2022 |title=Watch Nicki Minaj and Lil Baby in Video for New Song "Do We Have a Problem?" |url=https://www.complex.com/music/nicki-minaj-lil-baby-do-we-have-a-problem-video |magazine=[[Complex Networks|Complex]] |access-date=February 6, 2022 |archive-date=February 5, 2022 |archive-url=https://web.archive.org/web/20220205052009/https://www.complex.com/music/nicki-minaj-lil-baby-do-we-have-a-problem-video |url-status=live}}</ref> She described it as "fun, gutta, and back to the basics".<ref>{{Cite AV media |last=Musa |first=Masani |date=March 3, 2022 |title=Nicki Minaj Describes New Album As "Fun, Gutta, & Back To The Basic." |url=https://www.youtube.com/watch/0s5jmVZdnZs |url-status=live |archive-url=https://web.archive.org/web/20220303061849/https://www.youtube.com/watch?v=0s5jmVZdnZs |archive-date=March 3, 2022 |access-date=August 11, 2022 |website=Culture Unfiltered |via=YouTube}}</ref> In March, Minaj and fellow rapper [[Coi Leray]]'s collaboration "[[Blick Blick]]" was released—from the former's debut studio album, ''[[Trendsetter (Coi Leray album)|Trendsetter]]'' (2022).<ref>{{Cite magazine |last=Legaspi |first=Althea |date=March 18, 2022 |title=Coi Leray, Nicki Minaj Get What They Want on Commanding 'Blick Blick' |url=https://www.rollingstone.com/music/music-news/coi-leray-nicki-minaj-blick-blick-song-1323244/ |access-date=August 13, 2022 |magazine=[[Rolling Stone]] |archive-date=August 17, 2022 |archive-url=https://web.archive.org/web/20220817170830/https://www.rollingstone.com/music/music-news/coi-leray-nicki-minaj-blick-blick-song-1323244/ |url-status=live}}</ref> Later that month, Minaj surprise-released the standalone single "[[We Go Up (Nicki Minaj song)|We Go Up]]" featuring rapper [[Fivio Foreign]].<ref>{{Cite magazine |last=Dailey |first=Hannah |date=March 25, 2022 |title=Nicki Minaj Drops New Track 'We Go Up' With Fivio Foreign: Listen |url=https://www.billboard.com/music/music-news/nicki-minaj-fivio-foreign-we-go-up-listen-1235049332/ |access-date=August 14, 2022 |magazine=[[Billboard (magazine)|Billboard]] |archive-date=August 25, 2022 |archive-url=https://web.archive.org/web/20220825231804/https://www.billboard.com/music/music-news/nicki-minaj-fivio-foreign-we-go-up-listen-1235049332/ |url-status=live}}</ref> ln April, she appeared on English television host [[James Corden]]'s ''[[Carpool Karaoke]]'' segment, kickstarting the return of its broadcast on ''[[The Late Late Show with James Corden]]'' after a two-year hiatus.<ref>{{Cite magazine |last=Mier |first=Tomás |date=April 7, 2022 |title=Nicki Minaj Flaunts Her Best Adele, Gets Vulnerable, and Plays Clarinet on 'Carpool Karaoke' |url=https://www.rollingstone.com/music/music-news/nicki-minaj-james-corden-carpool-karaoke-1333825/ |magazine=[[Rolling Stone]] |access-date=April 7, 2022 |archive-date=April 7, 2022 |archive-url=https://web.archive.org/web/20220407052150/https://www.rollingstone.com/music/music-news/nicki-minaj-james-corden-carpool-karaoke-1333825/ |url-status=live}}</ref> In July, Minaj headlined the 2022 editions of [[Essence Music Festival#2022|Essence Music Festival]] in [[New Orleans]] and the [[Wireless Festival#2022|Wireless Festival]] in London.<ref name="essence_2022">{{Cite magazine |last=Muhammad |first=Latifah |date=June 29, 2022 |title=Essence Festival 2022: Where You Can Buy Tickets Before They Sell Out & How to Watch |url=https://www.billboard.com/culture/product-recommendations/essence-festival-how-to-buy-tickets-1235107391/ |magazine=[[Billboard (magazine)|Billboard]] |access-date=July 8, 2022 |archive-date=August 25, 2022 |archive-url=https://web.archive.org/web/20220825225917/https://www.billboard.com/culture/product-recommendations/essence-festival-how-to-buy-tickets-1235107391/ |url-status=live}}</ref><ref>{{Cite news |date=February 25, 2022 |title=Wireless: Nicki Minaj, Dave and Cardi B headline festival |language=en-GB |work=[[BBC News]] |url=https://www.bbc.com/news/uk-england-london-60520701 |access-date=February 25, 2022 |archive-date=February 25, 2022 |archive-url=https://web.archive.org/web/20220225203141/https://www.bbc.com/news/uk-england-london-60520701 |url-status=live}}</ref> On July 28, she released a two-minute trailer of her upcoming six-part [[Television documentary|docuseries]] titled "Nicki", produced by Canadian production company [[Bron Studios]].<ref name=":15"/><ref name=":16"/><ref>{{Cite magazine |last=Garcia |first=Thania |date=July 28, 2022 |title=Nicki Minaj Shares Trailer for Six-Part Docu-Series, 'Nicki' |url=https://variety.com/2022/film/news/nicki-minaj-trailer-docuseries-1235328842/ |magazine=[[Variety (magazine)|Variety]] |access-date=July 29, 2022 |archive-date=July 29, 2022 |archive-url=https://web.archive.org/web/20220729023831/https://variety.com/2022/film/news/nicki-minaj-trailer-docuseries-1235328842/ |url-status=live}}</ref> Minaj's radio show ''Queen Radio'' was revived and broadcast on Amp, a live audio service by [[Amazon (company)|Amazon]], over two years after its conclusion on Beats 1.<ref>{{cite magazine |last=Inman |first=Demicia |date=March 9, 2022 |title=Nicki Minaj Announces Return Of 'Queen Radio' On Amazon's Amp App |url=https://www.vibe.com/music/music-news/nicki-minaj-queen-radio-amazon-amp-1234651278/ |magazine=[[Vibe (magazine)|Vibe]] |access-date=September 17, 2024 |url-status=live |archive-date=June 23, 2024 |archive-url=https://web.archive.org/web/20240623141715/https://www.vibe.com/music/music-news/nicki-minaj-queen-radio-amazon-amp-1234651278/ }}</ref> The show premiered on August 11 on Amp; episodes were made available on [[Amazon Music]] after airing live.<ref>{{cite magazine |last=Grant |first=Shawn |date=August 9, 2022 |title=Nicki Minaj's 'Queen Radio' Set for August 11 Return on AMP |url=https://thesource.com/2022/08/09/nicki-minajs-queen-radio-set-for-august-11-return-on-amp/ |magazine=[[The Source (magazine)|The Source]] |access-date=September 17, 2024 |url-status=live |archive-date=September 30, 2022 |archive-url=https://web.archive.org/web/20220930162539/https://thesource.com/2022/08/09/nicki-minajs-queen-radio-set-for-august-11-return-on-amp/ }}</ref> It was discontinued in October 2023, as a result of the closure of Amp.<ref>{{cite web |last=Kyles |first=Yohance |date=October 5, 2023 |title=Amazon Ends Amp Live-Radio App, Home To Shows By Nicki Minaj, Joe Budden & More |url=https://allhiphop.com/news/amazon-ends-amp-app-nicki-minaj-joe-budden/ |website=[[AllHipHop]] |access-date=September 17, 2024 |archive-date=December 1, 2024 |archive-url=https://web.archive.org/web/20241201025341/https://allhiphop.com/news/amazon-ends-amp-app-nicki-minaj-joe-budden/ |url-status=live }}</ref>
"[[Super Freaky Girl]]", the lead single from Minaj's upcoming album, was released on August 12, 2022.<ref>{{Cite magazine |last=Gale |first=Ashley |date=August 3, 2022 |title=Nicki Minaj asks fans to help rename anticipated single 'Freaky Girl' |url=https://www.newsweek.com/nicki-minaj-asks-fans-help-rename-anticipated-single-freaky-girl-1730686 |magazine=[[Newsweek]] |access-date=August 7, 2022 |archive-date=August 7, 2022 |archive-url=https://web.archive.org/web/20220807034713/https://www.newsweek.com/nicki-minaj-asks-fans-help-rename-anticipated-single-freaky-girl-1730686 |url-status=live}}</ref><ref>{{Cite magazine |last=Cole |first=Alexander |date=August 5, 2022 |title=Nicki Minaj Reveals Cover Art For "Super Freaky Girl," Snippet Leaks Online |url=https://www.hotnewhiphop.com/nicki-minaj-reveals-release-date-for-super-freaky-girl-news.155986.html |magazine=[[HotNewHipHop]] |access-date=August 7, 2022 |archive-date=August 7, 2022 |archive-url=https://web.archive.org/web/20220807170637/https://www.hotnewhiphop.com/nicki-minaj-reveals-release-date-for-super-freaky-girl-news.155986.html |url-status=live}}</ref> Debuting atop the ''Billboard'' Hot 100, it marked Minaj's third US number-one, her first solo chart-topper, and the first solo song by a female rapper to debut at number one in the US in the 21st century.<ref name="SFG_hot100">{{Cite web |last=Robinson |first=Ellie |date=August 23, 2022 |title=Nicki Minaj becomes the first solo female rapper to have a single debut at Number One since 1998 |url=https://www.nme.com/news/music/nicki-minaj-becomes-the-first-solo-female-rapper-to-have-a-single-debut-at-number-one-since-1998-3295615 |website=[[NME]] |language=en-GB |access-date=August 23, 2022 |archive-date=July 13, 2023 |archive-url=https://web.archive.org/web/20230713044524/https://www.nme.com/news/music/nicki-minaj-becomes-the-first-solo-female-rapper-to-have-a-single-debut-at-number-one-since-1998-3295615 |url-status=live}}</ref> Minaj spoke on Instagram Live about her frustration with the song, which samples [[Rick James]]'s 1981 song "[[Super Freak]]", being considered in the pop categories instead of rap for the [[65th Annual Grammy Awards]]. Naming other examples of category mismatches, she said that if the song was not considered fitting for the rap category, [[Latto]]'s "[[Big Energy]]", which has the same producer, would also not be suitable for the category.<ref name="Variety 22">{{cite web|url=https://variety.com/2022/music/news/nicki-minaj-grammys-move-super-freaky-girl-rap-pop-category-latto-1235403285/|title=Nicki Minaj Calls Out Grammys for Moving 'Super Freaky Girl' From Rap to Pop, Claims Latto Is Treated Differently|work=[[Variety (magazine)|Variety]]|last=Willman|first=Chris|date=October 13, 2022|access-date=October 17, 2022|archive-date=October 16, 2022|archive-url=https://web.archive.org/web/20221016042155/https://variety.com/2022/music/news/nicki-minaj-grammys-move-super-freaky-girl-rap-pop-category-latto-1235403285/|url-status=live}}</ref> Minaj argued that her song was moved to pop to have less competition in the rap categories, alleging that it is part of a general corporate agenda to prop up newer artists over veterans.<ref>{{cite magazine|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-slams-grammys-again-super-freaky-girl-category-change-1235157367/|title=Nicki Minaj Slams Grammys Again Over 'Super Freaky Girl' Category Change: 'Certain Members Are Afraid to Keep It in the Competition'|magazine=[[Billboard (magazine)|Billboard]]|last=Kaufman|first=Gill|date=October 18, 2022|access-date=October 19, 2022|archive-date=October 18, 2022|archive-url=https://web.archive.org/web/20221018190929/https://www.billboard.com/music/rb-hip-hop/nicki-minaj-slams-grammys-again-super-freaky-girl-category-change-1235157367/|url-status=live}}</ref><ref name="Variety 22"/> She added that she would have no problem with it being moved from rap to pop if they were "all being treated fairly".<ref name=andregee>{{cite magazine|url=https://www.rollingstone.com/music/music-news/nicki-minaj-latto-twitter-grammys-1234611479/|title=Nicki Minaj and Latto Get Into a Twitter Spat After a Perplexing Grammy Choice|magazine=[[Rolling Stone]]|last=Gee|first=Andre|date=October 14, 2022|access-date=October 14, 2022|archive-date=October 14, 2022|archive-url=https://web.archive.org/web/20221014192357/https://www.rollingstone.com/music/music-news/nicki-minaj-latto-twitter-grammys-1234611479/|url-status=live}}</ref><ref name=rouhani>{{cite magazine|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-latto-twitter-fight-explained-1235156569/|title=Here's Why Nicki Minaj & Latto Are Beefing|magazine=[[Billboard (magazine)|Billboard]]|last=Rouhani|first=Neena|date=October 14, 2022|access-date=October 14, 2022|archive-date=October 15, 2022|archive-url=https://web.archive.org/web/20221015013849/https://www.billboard.com/music/rb-hip-hop/nicki-minaj-latto-twitter-fight-explained-1235156569/|url-status=live}}</ref>
On August 26, Minaj released her first [[greatest hits album]] titled ''[[Queen Radio: Volume 1]]'', comprising several hits from her previous projects and one new track, "[[Likkle Miss|Likkle Miss (Remix)]]" with Skeng.<ref>{{cite magazine|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-surprise-hits-compilation-queen-radio-volume-1-1235131402/|title=Nicki Minaj Drops Surprise Hits Compilation 'Queen Radio: Volume 1'|magazine=[[Billboard (magazine)|Billboard]]|last=Kaufman|first=Gil|date=August 26, 2022|access-date=October 28, 2024|archive-date=August 27, 2022|archive-url=https://web.archive.org/web/20220827114929/https://www.billboard.com/music/rb-hip-hop/nicki-minaj-surprise-hits-compilation-queen-radio-volume-1-1235131402/|url-status=live}}</ref><ref>{{cite magazine |url=https://www.papermag.com/nicki-minaj-greatest-hits-2657951503.html |title=Nicki Minaj Unleashes Greatest Hits Compilation, 'Queen Radio: Vol. 1' |magazine=[[Paper (magazine)|Paper]] |date=August 26, 2022 |access-date=October 28, 2024 |last=McCafferty |first=Kenna |archive-date=October 6, 2022 |archive-url=https://web.archive.org/web/20221006093657/https://www.papermag.com/nicki-minaj-greatest-hits-2657951503.html |url-status=live }}</ref> It debuted at number ten on the ''Billboard'' 200 with 32,000 units.<ref>{{cite magazine|url=https://www.billboard.com/music/chart-beat/dj-khaled-god-did-number-one-billboard-200-chart-1235134780/|title=DJ Khaled Lands Fourth No. 1 Album on ''Billboard'' 200 With 'God Did'|last=Caulfield|first=Keith|magazine=[[Billboard (magazine)|Billboard]]|date=September 4, 2022|access-date=October 28, 2024|archive-date=November 7, 2022|archive-url=https://web.archive.org/web/20221107151314/https://www.billboard.com/music/chart-beat/dj-khaled-god-did-number-one-billboard-200-chart-1235134780/|url-status=live}}</ref> Through the remainder of the year, Minaj appeared as a co-lead or featured artist on various collaborations: [[Yung Bleu]]'s "Love in the Way",<ref>{{Cite magazine |last1=Aniftos |first1=Rania |last2=Porter |first2=Sierra |date=September 22, 2022 |title=Yung Bleu Reveals He Was 'Nervous' to Film 'Love In The Way' Video With Nicki Minaj |url=https://www.billboard.com/music/rb-hip-hop/yung-bleu-nervous-to-film-love-in-the-way-video-nicki-minaj-1235143445/ |access-date=September 23, 2022 |magazine=Billboard |archive-date=September 23, 2022 |archive-url=https://web.archive.org/web/20220923144046/https://www.billboard.com/music/rb-hip-hop/yung-bleu-nervous-to-film-love-in-the-way-video-nicki-minaj-1235143445/ |url-status=live}}</ref> a remix of "Likkle Miss" called "The Fine Nine Remix" featuring eight female rappers,<ref>{{Cite web |last=Banks |first=Lamar |date=October 6, 2022 |title=Nicki Minaj Drops 'Likkle Miss' (Fine Nine) Remix With Spice, London Hill & More |url=https://www.hotnewhiphop.com/438604-nicki-minaj-drops-likkle-miss-fine-nine-remix-with-spice-london-hill-more |access-date=October 6, 2022 |website=HotNewHipHop |archive-date=October 6, 2022 |archive-url=https://web.archive.org/web/20221006093657/https://www.hotnewhiphop.com/438604-nicki-minaj-drops-likkle-miss-fine-nine-remix-with-spice-london-hill-more |url-status=live}}</ref> and [[YoungBoy Never Broke Again]]'s "I Admit".<ref>{{Cite magazine |last=Aniftos |first=Rania |date=October 21, 2022 |title=NBA YoungBoy & Nicki Minaj Team Up for 'I Admit': Listen |url=https://www.billboard.com/music/rb-hip-hop/nba-youngboy-nicki-minaj-i-admit-1235159011/ |access-date=October 28, 2022 |magazine=Billboard |archive-date=October 28, 2022 |archive-url=https://web.archive.org/web/20221028145807/https://www.billboard.com/music/rb-hip-hop/nba-youngboy-nicki-minaj-i-admit-1235159011/ |url-status=live}}</ref> The following month, Minaj collaborated with Lebanese singer [[Myriam Fares]] and Colombian singer [[Maluma]] on "[[Tukoh Taka]]" which was the official 2022 [[FIFA Fan Festival]] anthem.<ref>{{Cite magazine |last=Peters |first=Mitchell |date=November 20, 2022 |title=Fans Choose Nicki Minaj, Maluma & Myriam Fares' 'Tukoh Taka' as This Week's Favorite New Music |url=https://www.billboard.com/music/music-news/nicki-minaj-maluma-myriam-fares-tukoh-taka-2022-world-cup-best-new-music-1235174444/ |access-date=October 29, 2023 |magazine=Billboard |archive-date=November 27, 2022 |archive-url=https://web.archive.org/web/20221127101636/https://www.billboard.com/music/music-news/nicki-minaj-maluma-myriam-fares-tukoh-taka-2022-world-cup-best-new-music-1235174444/ |url-status=live}}</ref> Minaj emceed the [[2022 MTV Video Music Awards]] with [[LL Cool J]] and [[Jack Harlow]],<ref>{{Cite news |last=Carras |first=Christi |date=August 28, 2022 |title=VMAs 2022: Here's everything you need to know |url=https://www.latimes.com/entertainment-arts/music/story/2022-08-28/vmas-2022-what-time-how-to-watch |access-date=August 28, 2022 |work=[[Los Angeles Times]] |archive-date=August 29, 2022 |archive-url=https://web.archive.org/web/20220829021941/https://www.latimes.com/entertainment-arts/music/story/2022-08-28/vmas-2022-what-time-how-to-watch |url-status=live }}</ref> and hosted the [[2023 MTV Video Music Awards|2023 ceremony]].<ref>{{Cite magazine |last=Grein |first=Paul |date=September 8, 2023 |title=Nicki Minaj to 'Emcee' and Perform at 2023 MTV Video Music Awards |url=https://www.billboard.com/music/awards/nicki-minaj-2023-mtv-video-music-awards-emcee-perform-1235408718/ |url-status=live |magazine=Billboard |archive-url=https://web.archive.org/web/20230910214155/https://www.billboard.com/music/awards/nicki-minaj-2023-mtv-video-music-awards-emcee-perform-1235408718/ |archive-date=September 10, 2023 |access-date=December 8, 2023}}</ref> At the former ceremony, she received the [[Michael Jackson Video Vanguard Award]] and performed a medley of her hits as the recipient.<ref>{{cite web|url=https://www.stereogum.com/2197735/nicki-minaj-vmas-video-vanguard-award-performance/news/|title=Watch Nicki Minaj Accept Video Vanguard Award, Perform Career-Spanning Medley At The VMAs|website=[[Stereogum]]|date=August 28, 2022|first=James|last=Rettig|access-date=August 29, 2022|archive-date=August 29, 2022|archive-url=https://web.archive.org/web/20220829015723/https://www.stereogum.com/2197735/nicki-minaj-vmas-video-vanguard-award-performance/news/|url-status=live}}</ref>
On March 5, 2023, Minaj appeared as a surprise performer during [[Lil Wayne]]'s set at the [[Rolling Loud#California|Rolling Loud California]] festival.<ref>{{Cite magazine |last=Srour |first=Yousef |date=March 5, 2023 |title=Rolling Loud: Travis Scott's Set Cut Short Due to Curfew; Nicki Minaj, Justin Bieber, Kali Uchis Among Surprise Guests at L.A. Festival |url=https://variety.com/2023/music/news/rolling-loud-los-angeles-travis-scott-nicki-minaj-justin-bieber-1235543322/ |url-status=live |magazine=Variety |archive-url=https://web.archive.org/web/20230323151453/https://variety.com/2023/music/news/rolling-loud-los-angeles-travis-scott-nicki-minaj-justin-bieber-1235543322/ |archive-date=March 23, 2023 |access-date=July 23, 2023}}</ref> In April, she teamed up with [[Ice Spice]] on the remix to the latter's song "[[Princess Diana (song)|Princess Diana]]"—the first-ever release on Minaj's record label imprint, Heavy On It.<ref>{{Cite news |last=O'Donnell |first=Eoghan |date=April 14, 2023 |title=Ice Spice and Nicki Minaj remix hints at name of Minaj's record label |url=https://www.independent.co.uk/arts-entertainment/music/news/ice-spice-nicki-minaj-princess-diana-label-b2319768.html |url-status=live |archive-url=https://web.archive.org/web/20230426051133/https://www.independent.co.uk/arts-entertainment/music/news/ice-spice-nicki-minaj-princess-diana-label-b2319768.html |archive-date=April 26, 2023 |access-date=July 23, 2023 |work=[[The Independent]] }}</ref><ref>{{Cite web |last=Breihan |first=Tom |date=April 14, 2023 |title=Nicki Minaj Joins Ice Spice On 'Princess Diana' Remix: Listen |url=https://www.stereogum.com/2220338/nicki-minaj-joins-ice-spice-on-princess-diana-remix/music/ |url-status=live |archive-url=https://web.archive.org/web/20230722043034/https://www.stereogum.com/2220338/nicki-minaj-joins-ice-spice-on-princess-diana-remix/music/ |archive-date=July 22, 2023 |access-date=July 23, 2023 |website=Stereogum }}</ref> Peaking at number four on the Hot 100, the remix became the first collaboration between two women to top the Hot Rap Songs chart and made Minaj the woman with the most number-ones on the chart (10).<ref>{{Cite magazine |last=Anderson |first=Trevor |date=April 25, 2023 |title=Ice Spice & Nicki Minaj's 'Princess Diana' Reigns on Hot Rap Songs Chart |url=https://www.billboard.com/music/chart-beat/ice-spice-nicki-minaj-princess-diana-number-1-hot-rap-songs-chart-1235314984/ |url-status=live |magazine=Billboard |archive-url=https://web.archive.org/web/20230506121553/https://www.billboard.com/music/chart-beat/ice-spice-nicki-minaj-princess-diana-number-1-hot-rap-songs-chart-1235314984/ |archive-date=May 6, 2023 |access-date=July 23, 2023}}</ref> In April and May, she also featured on YoungBoy Never Broke Again's "[[WTF (YoungBoy Never Broke Again song)|WTF]]",<ref>{{Cite magazine |last=Lamarre |first=Carl |date=April 7, 2023 |title=YoungBoy Never Broke Again Recruits Nicki Minaj For Hard-Hitting Single 'WTF': Watch |url=https://www.billboard.com/music/rb-hip-hop/youngboy-never-broke-again-nicki-minaj-wtf-1235299435/ |url-status=live |magazine=Billboard |archive-url=https://web.archive.org/web/20230419083619/https://www.billboard.com/music/rb-hip-hop/youngboy-never-broke-again-nicki-minaj-wtf-1235299435/ |archive-date=April 19, 2023 |access-date=July 23, 2023}}</ref> German singer Kim Petras's "[[Alone (Kim Petras and Nicki Minaj song)|Alone]]",<ref>{{Cite magazine |last=Darville |first=Jordan |date=April 21, 2023 |title=Kim Petras enlists Nicki Minaj for "Alone" |url=https://www.thefader.com/2023/04/21/kim-petras-enlists-nicki-minaj-for-alone |url-status=live |magazine=The Fader |archive-url=https://web.archive.org/web/20230429143006/https://www.thefader.com/2023/04/21/kim-petras-enlists-nicki-minaj-for-alone |archive-date=April 29, 2023 |access-date=July 23, 2023}}</ref> and "[[Pound Town|Pound Town 2]]", the remix to rapper [[Sexyy Red]] and [[Tay Keith]]'s single, "Pound Town".<ref>
* {{Cite news |last=Kearns |first=Sarah |date=May 27, 2023 |title=Nicki Minaj Jumps on St. Louis Rapper Sexxy Red's "Pound Town 2" Remix |work=[[Hypebeast (company)|Hypebeast]] |url=https://hypebeast.com/2023/5/nicki-minaj-sexxy-red-pound-town-2-remix |url-status=live |access-date=July 23, 2023 |archive-url=https://web.archive.org/web/20230603170421/https://hypebeast.com/2023/5/nicki-minaj-sexxy-red-pound-town-2-remix |archive-date=June 3, 2023 }}
* {{Cite web |last=Gonzalez |first=Alex |date=May 26, 2023 |title=Sexyy Red And Tay Keith Enlist Nicki Minaj For 'Pound Town 2', An Even Saucier Remix Of Their Viral Hit |url=https://uproxx.com/music/sexyy-red-pound-town-2-nicki-minaj-tay-keith/ |url-status=live |archive-url=https://web.archive.org/web/20230615170454/https://uproxx.com/music/sexyy-red-pound-town-2-nicki-minaj-tay-keith/ |archive-date=June 15, 2023 |access-date=July 23, 2023 |website=[[Uproxx]]}}</ref> In June, Minaj and Ice Spice released their second collaboration, "[[Barbie World]]" with [[Europop]] band [[Aqua (band)|Aqua]], from the [[Barbie the Album|''Barbie'' film soundtrack]]. The song samples Aqua's 1997 single, "[[Barbie Girl]]". It reached number seven on the Hot 100 and extended Minaj's record as the female rapper with the most US top-tens (23).<ref>{{Cite magazine |last=Trust |first=Gary |date=July 3, 2023 |title=Morgan Wallen Adds 13th Week Atop Hot 100, Nicki Minaj, Ice Spice & Aqua Debut in Top 10 |url=https://www.billboard.com/music/chart-beat/morgan-wallen-nicki-minaj-ice-spice-aqua-hot-100-top-10-1235366064/ |url-status=live |magazine=Billboard |archive-url=https://web.archive.org/web/20230721191945/https://www.billboard.com/music/chart-beat/morgan-wallen-nicki-minaj-ice-spice-aqua-hot-100-top-10-1235366064/ |archive-date=July 21, 2023 |access-date=July 23, 2023}}</ref> Minaj made guest appearances on [[Young Thug]]'s "Money" with the late [[Juice Wrld]],<ref>{{Cite web |last=Minsker |first=Evan |date=June 27, 2023 |title=Young Thug Shares New Song "Money" With Juice WRLD and Nicki Minaj: Listen |url=https://pitchfork.com/news/young-thug-shares-new-song-money-with-juice-wrld-and-nicki-minaj-listen/ |url-status=live |archive-url=https://web.archive.org/web/20230702003522/https://pitchfork.com/news/young-thug-shares-new-song-money-with-juice-wrld-and-nicki-minaj-listen/ |archive-date=July 2, 2023 |access-date=July 23, 2023 |website=Pitchfork}}</ref> and [[Lil Uzi Vert]]'s "[[Endless Fashion]]", which charted in the top 20.<ref>{{Cite web |last=Pointer |first=Flisadam |date=June 30, 2023 |title=Lil Uzi Vert & Nicki Minaj's 'Endless Fashion' Just Gave Fans Another High-End Collaboration To Add To Their Collection |url=https://uproxx.com/music/lil-uzi-vert-nicki-minaj-endless-fashion/ |url-status=live |archive-url=https://web.archive.org/web/20230712063357/https://uproxx.com/music/lil-uzi-vert-nicki-minaj-endless-fashion/ |archive-date=July 12, 2023 |access-date=July 23, 2023 |website=Uproxx}}</ref><ref>{{Cite magazine |last=Zellner |first=Xander |date=July 10, 2023 |title=Lil Uzi Vert Debuts 18 Songs on Hot 100 From New Album 'Pink Tape', Reaches Historic Milestone |url=https://www.billboard.com/music/chart-beat/lil-uzi-vert-18-songs-hot-100-from-pink-tape-milestone-1235369128/ |url-status=live |magazine=Billboard |archive-url=https://web.archive.org/web/20230718124442/https://www.billboard.com/music/chart-beat/lil-uzi-vert-18-songs-hot-100-from-pink-tape-milestone-1235369128/ |archive-date=July 18, 2023 |access-date=July 23, 2023}}</ref> In October, she released "For All the Barbz", her freestyle of Drake's "[[All the Parties]]" (featuring [[Chief Keef]]), exclusively via her YouTube channel.<ref>{{Cite magazine |last=Kreps |first=Daniel |date=October 20, 2023 |title=Nicki Minaj Hops on Drake's "All the Parties' Beat on New Freestyle |url=https://www.rollingstone.com/music/music-news/nicki-minaj-for-all-the-barbz-drake-all-the-parties-freestyle-1234859031/ |access-date=March 2, 2024 |magazine=Rolling Stone |archive-date=March 2, 2024 |archive-url=https://web.archive.org/web/20240302082430/https://www.rollingstone.com/music/music-news/nicki-minaj-for-all-the-barbz-drake-all-the-parties-freestyle-1234859031/ |url-status=live }}</ref> With the [[2023 MTV Europe Music Awards]], she became the first black artist and female rapper to win [[MTV Europe Music Award for Best US Act|Best US Act]] in the award show's history.<ref>{{Cite magazine |last=Walcott |first=Escher |date=November 6, 2023 |title=Taylor Swift, Billie Eilish and Nicki Minaj Among 2023 MTV EMA Winners After Ceremony Cancellation |url=https://people.com/taylor-swift-billie-eilish-nicki-minaj-2023-mtv-ema-winners-8387655 |magazine=People |access-date=November 18, 2023 |archive-date=November 20, 2023 |archive-url=https://web.archive.org/web/20231120123644/https://people.com/taylor-swift-billie-eilish-nicki-minaj-2023-mtv-ema-winners-8387655 |url-status=live }}</ref> In December, Minaj performed on the Atlanta and Miami dates of [[iHeartRadio]]'s [[Jingle Ball Tour]] 2023.<ref>{{Cite magazine |last=Iasimone |first=Ashley |date=December 3, 2023 |title=Lil Wayne Replaces Nicki Minaj on Chicago Jingle Ball Lineup a Day Before the Show |url=https://www.billboard.com/music/music-news/nicki-minaj-cancels-chicago-jingle-ball-lil-wayne-1235531186/ |access-date=December 8, 2023 |magazine=Billboard |archive-date=December 8, 2023 |archive-url=https://web.archive.org/web/20231208030930/https://www.billboard.com/music/music-news/nicki-minaj-cancels-chicago-jingle-ball-lil-wayne-1235531186/ |url-status=live}}</ref> She conducted a New Year's Eve concert at the [[E11Even Miami|E11Even]] nightclub in Miami.<ref>{{Cite magazine |last=Davis |first=Ahmad |date=January 1, 2024 |title=Nicki Minaj's New Year's Resolution Goes Viral |url=https://www.rap-up.com/2024/01/01/nicki-minajs-new-years-resolution-goes-viral/ |access-date=January 8, 2024 |magazine=[[Rap-Up]] |archive-date=January 9, 2024 |archive-url=https://web.archive.org/web/20240109141328/https://www.rap-up.com/2024/01/01/nicki-minajs-new-years-resolution-goes-viral/ |url-status=live}}</ref>
Minaj's fifth studio album, ''[[Pink Friday 2]]'' was released on December 8, 2023, her birthday,<ref>{{Cite magazine |last=Mier |first=Tomás |date=December 8, 2023 |title=Nicki Minaj Finally Delivers 'Pink Friday 2' |url=https://www.rollingstone.com/music/music-news/nicki-minaj-pink-friday-2-release-1234919901/ |magazine=Rolling Stone |access-date=December 8, 2023 |archive-date=December 8, 2023 |archive-url=https://web.archive.org/web/20231208113548/https://www.rollingstone.com/music/music-news/nicki-minaj-pink-friday-2-release-1234919901/ |url-status=live}}</ref> after multiple date delays.<ref>
* {{Cite magazine |last=Mamo |first=Heran |date=June 5, 2023 |title=Nicki Minaj Announces Album Release Date |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-album-release-date-announcement-1235346506/ |url-status=live |magazine=Billboard |archive-url=https://web.archive.org/web/20230629192059/https://www.billboard.com/music/rb-hip-hop/nicki-minaj-album-release-date-announcement-1235346506/ |archive-date=June 29, 2023 |access-date=June 6, 2023}}
* {{Cite magazine |last=Lamarre |first=Carl |date=June 29, 2023 |title=Nicki Minaj Announces 'Pink Friday 2' & Album's Delayed Release Date: 'It'll Be Well Worth the Wait' |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-new-album-title-pink-friday-2-release-date-delayed-1235364150/ |url-status=live |magazine=Billboard |archive-url=https://web.archive.org/web/20230711060844/https://www.billboard.com/music/rb-hip-hop/nicki-minaj-new-album-title-pink-friday-2-release-date-delayed-1235364150/ |archive-date=July 11, 2023 |access-date=July 23, 2023}}
* {{Cite web |last=Bloom |first=Madison |date=October 25, 2023 |title=Nicki Minaj Delays New Album ''Pink Friday 2'' Again |url=https://pitchfork.com/news/nicki-minaj-delays-new-album-pink-friday-2-again/ |access-date=November 18, 2023 |website=Pitchfork |archive-date=October 25, 2023 |archive-url=https://web.archive.org/web/20231025180012/https://pitchfork.com/news/nicki-minaj-delays-new-album-pink-friday-2-again/ |url-status=live}}</ref> She released it solely through Republic Records, following the completion of her recording contract with Young Money.<ref>{{Cite tweet |user=NICKIMINAJ |number=1839880087946043535|title=Pink Friday 2 album was directly through Republic, as well.|date=September 28, 2024|access-date=September 30, 2024|archive-url=https://ghostarchive.org/archive/vHIG9|archive-date=January 25, 2026|url-status=live}}</ref><ref name="YM contract"/> The sequel to her debut album ''Pink Friday'', it is primarily a [[hip-hop]], [[contemporary R&B|R&B]] and [[pop rap]] record featuring several collaborations, and received generally favorable reviews from critics.<ref>{{cite web|url=https://www.nme.com/reviews/album/nicki-minaj-pink-friday-2-review-tracklist-lyrics-3555287|title=Nicki Minaj – ''Pink Friday 2'' review: blockbuster sequel lives up to the hype|website=[[NME]]|last=Levine|first=Nick|date=December 8, 2023|access-date=December 8, 2023|archive-date=December 8, 2023|archive-url=https://web.archive.org/web/20231208125138/https://www.nme.com/reviews/album/nicki-minaj-pink-friday-2-review-tracklist-lyrics-3555287|url-status=live}}</ref><ref>{{cite news|url=https://www.theguardian.com/music/2023/dec/08/nicki-minaj-pink-friday-2-review-glowing-endorsement-of-her-own-vagina|title=Nicki Minaj: ''Pink Friday 2'' review – 'You're never far from a glowing endorsement of her own vagina'|newspaper=[[The Guardian]]|last=Petridis|first=Alexis|author-link=Alexis Petridis|date=December 8, 2023|access-date=December 8, 2023|archive-date=December 8, 2023|archive-url=https://web.archive.org/web/20231208173440/http://www.theguardian.com/music/2023/dec/08/nicki-minaj-pink-friday-2-review-glowing-endorsement-of-her-own-vagina|url-status=live}}</ref><ref>{{cite web|url=https://www.rollingstone.com/music/music-features/five-takeaways-nicki-minajs-pink-friday-2-1234921068/|title=Nicki Minaj Waved a Wand Over the Entire Rap World — and Four Other Takeaways From 'Pink Friday 2'|publisher=[[RollingStone]]|last=Jeff Ihaza|first=Adam Gee|date=December 8, 2023|accessdate=November 12, 2024|archive-date=November 12, 2024|archive-url=https://web.archive.org/web/20241112104601/https://www.rollingstone.com/music/music-features/five-takeaways-nicki-minajs-pink-friday-2-1234921068/|url-status=live}}</ref> The album debuted atop the ''Billboard'' 200, with 228,000 album-equivalent units, marking Minaj's first album [[Pink Friday: Roman Reloaded|since 2012]] to do so. The album broke multiple records, including making Minaj the female rapper with the most US number-one albums (3) in chart history,<ref name="pf2_charttop">{{Cite magazine |last=Caulfield |first=Keith |date=December 17, 2023 |title=Nicki Minaj Scores Third No. 1 Album on Billboard 200 With 'Pink Friday 2' |url=https://www.billboard.com/music/chart-beat/nicki-minaj-pink-friday-2-number-one-debut-billboard-200-albums-chart-1235560607/ |magazine=Billboard |access-date=December 18, 2023 |archive-date=December 18, 2023 |archive-url=https://web.archive.org/web/20231218003029/https://www.billboard.com/music/chart-beat/nicki-minaj-pink-friday-2-number-one-debut-billboard-200-albums-chart-1235560607/ |url-status=live }}</ref> earning the highest first-week vinyl sales for a female rap album in history,<ref name="pf2_charttop"/><ref name="PF2_Spotify">{{Cite magazine |last=Corrine |first=Amber |date=December 11, 2023 |title=Nicki Minaj's 'Pink Friday 2' Goes Gold, Breaks Spotify Record 2 Days After Its Release |url=https://www.vibe.com/music/music-news/nicki-minaj-pink-friday-2-gold-spotify-records-1234830449/ |access-date=June 11, 2024 |magazine=[[Vibe (magazine)|Vibe]] |archive-date=June 12, 2024 |archive-url=https://web.archive.org/web/20240612185133/https://www.vibe.com/music/music-news/nicki-minaj-pink-friday-2-gold-spotify-records-1234830449/ |url-status=live }}</ref> Fourteen tracks from the album appeared on the ''Billboard'' Hot 100, two of which charted in the top 40—"[[Everybody (Nicki Minaj song)|Everybody]]" featuring [[Lil Uzi Vert]] and "[[Needle (song)|Needle]]" featuring Drake.<ref>{{Cite magazine |last=Zellner |first=Xander |date=December 18, 2023 |title=Nicki Minaj Charts 14 Songs From 'Pink Friday 2' on the Hot 100 |url=https://www.billboard.com/music/chart-beat/nicki-minaj-14-pink-friday-2-songs-hot-100-1235561196/ |magazine=[[Billboard (magazine)|Billboard]] |access-date=December 19, 2023 |archive-date=December 18, 2023 |archive-url=https://web.archive.org/web/20231218222411/https://www.billboard.com/music/chart-beat/nicki-minaj-14-pink-friday-2-songs-hot-100-1235561196/ |url-status=live}}</ref> Two deluxe versions of ''Pink Friday 2'' were released.<ref>{{Cite web |last=Horvath |first=Zachary |date=December 13, 2023 |title=Nicki Minaj Preaches Self-Worth On "Love Me Enough" With Monica And Keyshia Cole |url=https://www.hotnewhiphop.com/744317-nicki-minaj-love-me-enough-single |access-date=February 22, 2024 |website=[[HotNewHipHop]] |archive-date=January 4, 2024 |archive-url=https://web.archive.org/web/20240104173740/https://www.hotnewhiphop.com/744317-nicki-minaj-love-me-enough-single |url-status=live }}</ref><ref>{{Cite news |last=Singleton |first=Isaiah |date=December 19, 2023 |title=The Re-emergence of Nicki Minaj: Pink Friday 2 |url=https://theatlantavoice.com/the-re-emergence-of-nicki-minaj-pink-friday-2/ |access-date=February 22, 2024 |work=[[The Atlanta Voice]] |archive-date=February 22, 2024 |archive-url=https://web.archive.org/web/20240222044803/https://theatlantavoice.com/the-re-emergence-of-nicki-minaj-pink-friday-2/ |url-status=live}}</ref><ref>{{Cite magazine |last=Turner-Williams |first=Jaelani |date=January 13, 2024 |title=Nicki Minaj and Future Double Up on 'Pink Friday 2' Collaboration "Press Play" |url=https://www.complex.com/music/a/jaelaniturnerwilliams/nicki-minaj-future-new-song-press-play |access-date=February 22, 2024 |magazine=Complex |publisher=[[Complex Networks]] |archive-date=January 13, 2024 |archive-url=https://web.archive.org/web/20240113181919/https://www.complex.com/music/a/jaelaniturnerwilliams/nicki-minaj-future-new-song-press-play |url-status=live }}</ref> The album was certified platinum by the RIAA in March 2024.<ref name=":5">{{Cite magazine |last=Corrine |first=Amber |date=March 29, 2024 |title=Nicki Minaj's "Super Bass" Dethrones Cardi B's "Bodak Yellow" As Highest Certified Single By A Woman Rapper |url=https://www.vibe.com/music/music-news/nicki-minajs-super-bass-more-certified-cardi-b-bodak-yellow-1234865458/ |access-date=March 31, 2024 |magazine=Vibe |archive-date=March 31, 2024 |archive-url=https://web.archive.org/web/20240331104132/https://www.vibe.com/music/music-news/nicki-minajs-super-bass-more-certified-cardi-b-bodak-yellow-1234865458/ |url-status=live}}</ref>
"[[Red Ruby da Sleeze]]" was released as the album's second single in March 2023,<ref>{{Cite magazine |last1=Blistein |first1=Jon |date=March 3, 2023 |title=You Can't Walk in Her Crocs: Nicki Minaj Drops First Song of 2023 'Red Ruby Da Sleeze' |url=https://www.rollingstone.com/music/music-news/nicki-minaj-new-song-red-ruby-da-sleeze-1234690245/ |url-status=live |magazine=Rolling Stone |archive-url=https://web.archive.org/web/20230310154608/https://www.rollingstone.com/music/music-news/nicki-minaj-new-song-red-ruby-da-sleeze-1234690245/ |archive-date=March 10, 2023 |access-date=July 23, 2023 |url-access=subscription}}</ref> followed by the third single "[[Last Time I Saw You]]" in September 2023.<ref>{{Cite magazine |last=Anderson |first=Trevor |date=September 12, 2023 |title=Nicki Minaj Breaks Record for Most No. 1s on R&B/Hip-Hop Digital Song Sales With 'Last Time I Saw You' |url=https://www.billboard.com/pro/nicki-minaj-last-time-i-saw-you-rb-hip-hop-digital-song-sales-chart-record/ |magazine=Billboard |access-date=November 18, 2023 |url-access=subscription |archive-date=September 14, 2023 |archive-url=https://web.archive.org/web/20230914003835/https://www.billboard.com/pro/nicki-minaj-last-time-i-saw-you-rb-hip-hop-digital-song-sales-chart-record/ |url-status=live }}</ref> "Everybody" and "[[FTCU]]" became subsequent singles in January 2024;<ref>{{Cite magazine |date=January 9, 2024 |title=Your Radio Add Recaps |url=https://hitsdailydouble.com/news%26id%3D339106 |url-status=live |archive-url=https://web.archive.org/web/20240109234506/https://hitsdailydouble.com/news%26id%3D339106 |archive-date=January 9, 2024 |access-date=January 9, 2024 |magazine=[[Hits (magazine)|Hits]] }}</ref><ref>* {{Cite magazine |date=January 9, 2024 |title=Your Radio Add Recaps |url=https://hitsdailydouble.com/news%26id%3D339107 |url-status=live |archive-url=https://web.archive.org/web/20240109235200/https://hitsdailydouble.com/news%26id%3D339107 |archive-date=January 9, 2024 |access-date=January 10, 2024 |magazine=[[Hits (magazine)|Hits]] }}
* {{Cite magazine |last=McIntyre |first=Hugh |date=January 10, 2024 |title=Nicki Minaj Is Trying To Help Her Latest Single Up The Charts–And Her Plan May Work |url=https://www.forbes.com/sites/hughmcintyre/2024/01/10/nicki-minaj-is-trying-to-help-her-latest-single-up-the-chartsand-her-plan-may-work/ |access-date=January 13, 2024 |magazine=Forbes |archive-date=January 13, 2024 |archive-url=https://web.archive.org/web/20240113202209/https://www.forbes.com/sites/hughmcintyre/2024/01/10/nicki-minaj-is-trying-to-help-her-latest-single-up-the-chartsand-her-plan-may-work/ |url-status=live }}</ref> "Red Ruby da Sleeze" and "FTCU" reached the top 15 on the Hot 100.<ref>{{Cite magazine |last1=Anderson |first1=Trevor |date=March 16, 2023 |title=Nicki Minaj Scores Back-to-Back No. 1 Debuts on Hot Rap Songs Chart |url=https://www.billboard.com/pro/nicki-minaj-red-ruby-sleeze-number-1-rap-songs-chart/ |url-status=live |magazine=Billboard |archive-url=https://web.archive.org/web/20230406175605/https://www.billboard.com/pro/nicki-minaj-red-ruby-sleeze-number-1-rap-songs-chart/ |archive-date=April 6, 2023 |access-date=March 16, 2023}}</ref><ref>{{Cite magazine |last=McIntyre |first=Hugh |date=January 19, 2024 |title=Nicki Minaj Scores Three New No. 1 Hit Singles On The ''Billboard'' Charts |url=https://www.forbes.com/sites/hughmcintyre/2024/01/19/nicki-minaj-scores-three-new-no-1-hit-singles-on-the-billboard-charts/ |access-date=January 21, 2024 |magazine=Forbes |archive-date=January 21, 2024 |archive-url=https://web.archive.org/web/20240121021929/https://www.forbes.com/sites/hughmcintyre/2024/01/19/nicki-minaj-scores-three-new-no-1-hit-singles-on-the-billboard-charts/ |url-status=live }}</ref> On January 26, 2024, Megan Thee Stallion released the song "[[Hiss (song)|Hiss]]", which was believed to include jabs at Minaj and her husband.<ref>{{cite web | url=https://www.xxlmag.com/nicki-minaj-megan-thee-stallion-lying-drink-pregnant/ | title=Nicki Minaj Accuses Megan Thee Stallion of Lying and Telling Nicki to Drink when Pregnant | date=January 27, 2024|work=[[XXL (magazine)|XXL]]|access-date=September 5, 2024}}</ref> Soon after, Minaj did an Instagram live where she previewed a rap she made on the live, titled "[[Big Foot (Nicki Minaj song)|Big Foot]]", which was released on January 28.<ref>{{Cite magazine|url=https://www.billboard.com/music/music-news/nicki-minaj-megan-thee-stallion-response-bigfoot-stream-1235591129/|title=Nicki Minaj Marks Her Turf With Megan Thee Stallion Response Track 'Big Foot': Stream It Now|magazine=Billboard|date=January 29, 2024|access-date=September 5, 2024}}</ref><ref>{{cite web|url=https://www.instagram.com/p/C2tpFsFrTpI/|title=Imagine being backed by a machine & going up against me ...|author=Nicki Minaj|via=Instagram|date=January 30, 2024|access-date=September 6, 2024}}</ref> The song broke first-day records for the highest solo female rap debut in [[Apple Music]] history, and the biggest solo female rap debut on [[Spotify]] and [[YouTube]] in 2024.<ref>{{cite web|url=https://www.hotnewhiphop.com/758432-nicki-minaj-big-foot-spotify-record-2024-hip-hop-news|title=Nicki Minaj Nabs Another Record-Breaking Achievement With "Big Foot"|date=30 January 2024|access-date=30 January 2024|work=[[HotNewHipHop]]|first=Gabriel Bras|last=Nevares|archive-date=January 30, 2024|archive-url=https://web.archive.org/web/20240130200110/https://www.hotnewhiphop.com/758432-nicki-minaj-big-foot-spotify-record-2024-hip-hop-news|url-status=live}}</ref> It debuted at number 23 on the ''Billboard'' Hot 100.<ref>{{cite magazine|url=https://www.billboard.com/business/business-news/nicki-minaj-megan-thee-stallion-feud-money-maker-1235599613/|title=The Beef Business: Nicki Minaj & Megan Thee Stallion's Feud Has Been a Money-Maker|date=February 5, 2024|access-date=February 5, 2024|last=Untenberger|first=Andrew|magazine=[[Billboard (magazine)|Billboard]]|archive-date=February 5, 2024|archive-url=https://web.archive.org/web/20240205235142/https://www.billboard.com/business/business-news/nicki-minaj-megan-thee-stallion-feud-money-maker-1235599613/|url-status=live}}</ref> The song also extended Minaj's record as the female rapper with the most, and woman with the second-most, top-tens (46) on the [[Hot R&B/Hip-Hop Songs]] chart, behind [[Aretha Franklin]] (52).<ref>{{Cite magazine |last=Anderson |first=Trevor |date=February 7, 2024 |title='Big Foot' Spotted in Top 10 on Hot R&B/Hip-Hop Songs Chart |url=https://www.billboard.com/music/chart-beat/nicki-minaj-big-foot-top-10-rb-hip-hop-songs-chart-debut-1235601621/ |access-date=February 9, 2024 |magazine=Billboard |archive-date=February 8, 2024 |archive-url=https://web.archive.org/web/20240208084445/https://www.billboard.com/music/chart-beat/nicki-minaj-big-foot-top-10-rb-hip-hop-songs-chart-debut-1235601621/ |url-status=live}}</ref>
In support of ''Pink Friday 2'', Minaj embarked on its [[Pink Friday 2 World Tour|namesake world tour]], her most extensive tour yet, from March to October 2024.<ref>{{Cite magazine |last1=Wu |first1=Valerie |last2=Horowitz |first2=Steven J. |date=May 29, 2024 |title=Nicki Minaj Announces Second North American Leg of 'Pink Friday 2 World Tour' |url=https://variety.com/2024/music/news/nicki-minaj-pink-friday-tour-dates-1235793368/ |access-date=May 31, 2024 |magazine=Variety}}</ref><ref>{{Cite web |last=Burton |first=Poppy |date=12 October 2024 |title=Watch Nicki Minaj bring out Sexyy Red and 50 Cent on the final night of the 'Pink Friday 2' tour |url=https://www.nme.com/news/music/watch-nicki-minaj-bring-out-sexyy-red-and-50-cent-at-the-closing-night-of-the-pink-friday-2-tour-3802116/ |access-date=October 12, 2024 |website=[[NME]]}}</ref> The tour included her headlining slots at ten festivals.<ref>Festivals headlined by Minaj during the Pink Friday 2 World Tour:
* {{Cite magazine |last=Horowitz |first=Steven J. |date=November 14, 2023 |title=Nicki Minaj, Post Malone, Lil Uzi Vert to Headline Rolling Loud California 2024 |url=https://variety.com/2023/music/news/nicki-minaj-post-malone-lil-uzi-vert-rolling-loud-california-1235790476/ |access-date=November 18, 2023 |magazine=Variety }}
* {{Cite web |last=Williams |first=Aaron |date=December 11, 2023 |title=Is Nicki Minaj Performing At The 2024 Dreamville Festival? |url=https://uproxx.com/music/is-nicki-minaj-performing-dreamville-festival-2024/ |access-date=December 11, 2023 |website=[[Uproxx]] }}
* {{Cite magazine |last=Zasada |first=Zuzanna |date=February 20, 2024 |title=Orange Warsaw Festival 2024: W końcu poznaliśmy pierwszych artystów, którzy wystąpią na festiwalu. Na headlinerkę czekali wszyscy. Hot, hot, hot |trans-title=Orange Warsaw Festival 2024: We finally know the first artists who will perform at the festival. Everyone was waiting for the headliner. Hot, hot, hot |url=https://www.glamour.pl/artykul/orange-warsaw-festival-2024-w-koncu-poznalismy-pierwszych-artystow-ktorzy-wystapia-na-festiwalu-na-headlinerke-czekali-wszyscy-hot-hot-hot-240220122020 |access-date=February 20, 2024 |magazine=[[Glamour (magazine)|Glamour Poland]] |language=PL }}
* {{Cite web |last=Williams |first=Kyann-Sian |date=January 23, 2024 |title=Nicki Minaj added to Afro Nation Portugal 2024 line-up |url=https://www.nme.com/news/music/nicki-minaj-is-added-to-afro-nation-portugal-line-up-2024-3575593 |access-date=January 26, 2024 |website=[[NME]] }}
* {{Cite news |date=May 11, 2024 |last=Simpara |first=Mahamadou |title=Mawazine Festival Roars Back with Nicki Minaj, Ramadan & More |url=https://www.moroccoworldnews.com/2024/05/362584/mawazine-festival-roars-back-with-nicki-minaj-ramadan-and-more/ |access-date=May 23, 2024 |work=[[Morocco World News]] |archive-date=December 1, 2024 |archive-url=https://web.archive.org/web/20241201000248/https://www.moroccoworldnews.com/2024/05/362584/mawazine-festival-roars-back-with-nicki-minaj-ramadan-and-more |url-status=dead }}
* {{Cite magazine |date=February 21, 2024 |title=Nicki Minaj e Jorja Smith dal vivo in Italia quest'estate |trans-title=Nicki Minaj and Jorja Smith live in Italy this summer |url=https://billboard.it/eventi/nicki-minaj-milano-jorja-smith/2024/02/21154542/ |access-date=February 22, 2024 |magazine=Billboard Italia |language=it |archive-date=December 1, 2024 |archive-url=https://web.archive.org/web/20241201000826/https://billboard.it/eventi/nicki-minaj-milano-jorja-smith/2024/02/21154542/ |url-status=dead }}
* {{Cite magazine |last=Horowitz |first=Steven J. |date=April 11, 2024 |title=Nicki Minaj, Travis Scott and Playboi Carti to Headline Rolling Loud Europe 2024 |url=https://variety.com/2024/music/news/rolling-loud-europe-2024-nicki-minaj-travis-scott-playboi-carti-1235967463/ |access-date=April 30, 2024 |magazine=Variety }}
* {{Cite news |last=Schär |first=Saskia |date=March 5, 2024 |title=Apache 207 und Nicki Minaj kommen ans Openair Frauenfeld |trans-title=Apache 207 and Nicki Minaj come to the Openair Frauenfeld |url=https://www.blick.ch/people-tv/musik/line-up-veroeffentlicht-apache-207-und-nicki-minaj-kommen-ans-openair-frauenfeld-id19502962.html |access-date=March 5, 2024 |work=[[Blick]] |language=gsw }}
* {{Cite news |last=Collins |first=Riyah |date=January 29, 2024 |title=Nicki Minaj tops Wireless festival line-up with 21 Savage and Doja Cat |url=https://www.bbc.com/news/newsbeat-68127163 |access-date=February 20, 2024 |work=[[BBC News]] }}
* {{Cite web |last=Lefèbvre |first=Eléna |date=February 5, 2024 |title=Les Ardentes: Nicki Minaj confirmée, l'été des festivals s'annonce épique |trans-title=Les Ardentes: Nicki Minaj confirmed, the summer of festivals promises to be epic |url=https://www.rtbf.be/article/les-ardentes-nicki-minaj-confirmee-lete-des-festivals-sannonce-epique-11324511 |access-date=February 20, 2024 |publisher=[[RTBF]] |language=fr }}</ref> It concluded as the highest-grossing tour by a female rapper in history and the fourth-highest-grossing tour by a rapper or hip hop artist at the time, with over $108 million in revenue, based on the first 70 shows.<ref name="BB tour"/><ref name="BB HH"/><ref name="ranking"/> In December 2025, RapTV ranked it as the sixth-highest-grossing tour of all time by a rap or hip–hop artist.<ref name="RapTV tour">{{Cite magazine |last=Coates|first=Taiyo |date=December 8, 2025 |title=The Highest-Grossing Rap Tours of All Time |url=https://raptv.com/article/top-5-highest-grossing-rap-tours-of-all-time/|access-date=December 24, 2025 |magazine=RapTV}}</ref> It was nominated for Hip-Hop Tour of the Year at the 2025 [[Pollstar|''Pollstar'' Awards]].<ref>{{Cite magazine |last=Grein |first=Paul |date=November 8, 2024 |title=Taylor Swift, Olivia Rodrigo, Coldplay & More Nominated for 2025 Pollstar Awards |url=https://www.billboard.com/pro/pollstar-awards-2025-nominations-taylor-swift-zach-bryan/ |url-access=subscription |access-date=November 10, 2024 |magazine=Billboard}}</ref> Minaj was nominated for [[Grammy Award for Best Rap Song|Best Rap Song]] and [[Grammy Award for Best Song Written for Visual Media|Best Song Written for Visual Media]], along with Ice Spice, for "Barbie World" at the [[66th Annual Grammy Awards]].<ref>{{Cite news |last1=Horton |first1=Adrian |last2=Lee |first2=Benjamin |date=February 5, 2024 |title=Grammy awards 2024: list of winners |url=https://www.theguardian.com/music/2024/feb/04/grammy-awards-2024-list-of-winners/ |access-date=February 5, 2024 |work=The Guardian |archive-date=February 5, 2024 |archive-url=https://web.archive.org/web/20240205000645/https://www.theguardian.com/music/2024/feb/04/grammy-awards-2024-list-of-winners |url-status=live}}</ref> She led the nominations among women for the [[BET Awards 2024]], with six,<ref>{{Cite magazine |last=Garcia |first=Thania |date=May 16, 2024 |title=Drake and Nicki Minaj Lead 2024 BET Nominations List |url=https://variety.com/2024/music/news/drake-nicki-minaj-bet-awards-nominations-1236006666/ |access-date=August 27, 2024 |magazine=Variety}}</ref> and won [[BET Award for Best Female Hip Hop Artist|Best Female Hip Hop Artist]]—her seventh win in the category, and first since 2016.<ref>{{Cite magazine |last=Coleman II |first=C. Vernon |date=July 1, 2024 |title=Nicki Minaj Wins Best Female Hip Hop Artist at 2024 BET Awards |url=https://www.xxlmag.com/nicki-minaj-wins-2024-bet-awards/ |access-date=August 27, 2024 |magazine=[[XXL (magazine)|XXL]]}}</ref> At the [[2024 BET Hip Hop Awards]], ''Pink Friday 2'' became the first album by a solo woman to win [[BET Hip Hop Award for Album of the Year|Album of the Year]].<ref>{{Cite web |last=Kelly |first=Tyler Damara |date=October 16, 2024 |title=Nicki Minaj is the first solo woman to win Album of the Year at BET Hip Hop Awards |url=https://www.thelineofbestfit.com/news/nicki-minaj-is-the-first-solo-woman-to-win-album-of-the-year-at-bet-hip-hop-awards |access-date=October 29, 2024 |website=[[The Line of Best Fit]]}}</ref> Minaj was set to release a deluxe version of the album, titled ''Gag City Reloaded'',<ref>{{Cite magazine |last=Saponara |first=Michael |date=August 26, 2024 |title=Nicki Minaj Reveals 'Pink Friday 2: Gag City Reloaded' Deluxe Album Release Date |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-pink-friday-2-gag-city-reloaded-deluxe-release-date-1235761617/ |access-date=August 27, 2024 |magazine=Billboard}}</ref> but announced in September 2024 that she had scrapped it in favor of making her sixth studio album.<ref>{{Cite magazine |last=Saponara |first=Michael |date=September 16, 2024 |title=Nicki Minaj Announces 'Pink Friday 3' Album to Replace 'PF2: Gag City Reloaded' Deluxe |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-new-album-pink-friday-3-replaces-pink-friday-2-deluxe-1235775864/ |access-date=September 17, 2024 |magazine=[[Billboard (magazine)|Billboard]]}}</ref>
== Artistry ==
=== Musical style and lyrics ===
Minaj is known for her animated rap [[Flow (music)|flow]], witty lyrics, and musical versatility.<ref name="AM">{{Cite web |last=Thomas |first=Fred |title=Nicki Minaj Biography |url=https://www.allmusic.com/artist/nicki-minaj-mn0001013175/biography |url-status=live |archive-url=https://web.archive.org/web/20200803151944/https://www.allmusic.com/artist/nicki-minaj-mn0001013175/biography |archive-date=August 3, 2020 |access-date=November 1, 2021 |website=AllMusic |language=en}}</ref><ref name="billboardgreatest1"/> Her rapping is distinctive for its varying cadence and speed, and use of [[alter ego]]s and accents, including [[Cockney speech|British cockney]].<ref name=":3">{{Cite news|url=https://mic.com/articles/116618/13-lyrics-that-prove-nicki-minaj-is-legitimately-a-great-rapper#.bzG7UsE5e|title=13 Lyrics That Prove Nicki Minaj Is One of the Greatest Rappers of All Time|work=Mic|access-date=March 9, 2018|language=en|archive-date=July 31, 2023|archive-url=https://web.archive.org/web/20230731071901/https://www.mic.com/articles/116618/13-lyrics-that-prove-nicki-minaj-is-legitimately-a-great-rapper#.bzG7UsE5e|url-status=live}}</ref><ref name=NYT20120330 /> In 2010, Minaj said that she aimed to be "a balanced artist ... this is all years of me learning me and my style, and deciding to do something different that would get everyone's attention."<ref>{{cite web |author=Nigel D. |url=https://www.digitalspy.com/music/a239528/minaj-i-want-to-be-a-balanced-artist/ |title=Minaj: 'I want to be a balanced artist' |work=[[Digital Spy]] |date=July 6, 2010 |access-date=August 26, 2024 |archive-url=https://web.archive.org/web/20240826082757/https://www.digitalspy.com/music/a239528/minaj-i-want-to-be-a-balanced-artist/ |archive-date=August 26, 2024 |url-status=live}}</ref> She stated that animation and performance was always a part of her and came naturally in her artistry, and her performing arts background also contributed to it.<ref>{{cite web|url=https://www.complex.com/music/a/zach-frydenlund/nicki-minaj-crwn-interview|title=Nicki Minaj Talks Her Creative Process, Lil Wayne Wanting to Leave Cash Money, and More On CRWN With Elliott Wilson|work=[[Complex (magazine)|Complex]]|date=December 19, 2014|access-date=February 23, 2025|archive-url=https://web.archive.org/web/20230918143932/https://www.complex.com/music/a/zach-frydenlund/nicki-minaj-crwn-interview|archive-date=September 18, 2023|url-status=live}}</ref><ref>{{cite web|url=https://www.youtube.com/watch?v=_ka8S4O10AU|title=Nicki Minaj: The 'Pink Friday 2' Interview|publisher=[[Apple Music]]|via=YouTube|time=30:05|date=January 4, 2024|access-date=September 3, 2024}}</ref> She regards rapping as both an art and a sport, enjoys changing up her rap flow, and sometimes spends hours revising her work. "I love that people never know what Nicki they are going to get on a particular song. I like bringing out a different side."<ref>{{cite web|url=https://www.vogue.com/article/nicki-minaj-interview-feminism-pinkprint|title=Newly Single Nicki Minaj on Feminism, Meek Mill, and Rapping at 50|date=February 12, 2015 |archive-url=https://web.archive.org/web/20210617033456/https://www.vogue.com/article/nicki-minaj-interview-feminism-pinkprint|archive-date=June 17, 2021}}</ref>
Minaj has made use of [[metaphor]]s, [[punch line]]s, and [[word play]]. She often incorporates personas, using different accents or cadences. Marc Hogan of ''[[Spin (magazine)|Spin]]'' said that through her character Roman, Minaj "tap[ped] into the fire-breathing, giddily nihilistic spirit that drew so many rap heads to her mixtapes ... as well as her guest appearances on others' tracks.<ref>{{Cite web|url=https://www.spin.com/2011/12/nicki-minajs-roman-zolanski-alter-ego-brief-biography/|title=Nicki Minaj's Roman Zolanski Alter Ego: A Brief Biography|work=Spin|date=December 5, 2011|access-date=September 3, 2024}}</ref> [[Jon Caramanica]] of ''[[The New York Times]]'' called Minaj a "nimble, evocative rapper" and "intricate lyricist" with a "gift for comic accents and unexpected turns of phrase. She's a walking exaggeration, outsize in sound, personality and look. And she's a rapid evolver, discarding old modes as easily as adopting new ones."<ref name="NYT20120330">{{cite news|last=Caramanica|first=Jon|date=March 30, 2012|title=A Singular Influence|work=The New York Times|url=https://www.nytimes.com/2012/04/01/arts/music/nicki-minaj-is-the-influential-leader-of-hip-hop.html?pagewanted=all|url-access=registration|access-date=June 9, 2020|archive-date=January 17, 2024|archive-url=https://web.archive.org/web/20240117070531/https://www.nytimes.com/2012/04/01/arts/music/nicki-minaj-is-the-influential-leader-of-hip-hop.html|url-status=live}}</ref> ''[[PopMatters]]''{{'}} Nick Soulsby called her lyrics and delivery "a firework display of sharp metaphors, humorous comebacks and putdowns, a rainbow of emotion backed up by a talent for wordplay", adding that "while many rappers lean into a single style from start-to-end of a song — or even a career — Minaj is alive to the potential of her performances and challenges herself on the mic".<ref name="PM 20"/> Writing for ''[[Paper (magazine)|Paper]]'' magazine, Liza Dye said that Minaj's first mixtape "established her lyrical talent and knack for creating a colorful world filled with witty rap references, vocal impressions and memorable characters."<ref>{{cite web|url=https://www.papermag.com/break-the-internet-nicki-minaj-sexy-full-story|title=Break the Internet: Minaj à Trois|work=[[Paper (magazine)|Paper]]|date=November 15, 2017|access-date=September 3, 2017}}</ref> ''The New York Times''{{'}} Roxane Gay characterized Minaj's work by "urgent lyrics, spitting in a range of voices and accents", containing "wit and sly humor", and her raps ranging "from bold and aggressive, to coquettish, to wanton and sultry, with a soupçon of women's empowerment", while her pace "is often breathless but her diction is impeccable."<ref name="NYT 17"/>
[[AllMusic]] described her artistry as a "unique combination of ferocity and humor, and a rap style built on razor-sharp wordplay, mercilessly blunt lyricism, and delivery that turns on a dime from sugary to snarling".<ref name="AM"/> ''[[Evening Standard]]''{{'}}s Jochan Embley wrote that her lyrics "stare gender and race in the eye", and are "fierce, hilarious, bombastic and deadly serious, often all bundled together in one. If she's not taking aim at other rappers ... she's promoting body confidence or providing a commentary on social issues."<ref name="ES 18">{{cite web|url=https://www.standard.co.uk/culture/music/nicki-minaj-best-lyrics-verses-songs-features-queen-album-release-a3908781.html|title=Ten of Nicki Minaj's best ever lyrics and verses|work=[[Evening Standard]]|date=August 13, 2018|first=Jochan|last=Embley|access-date=September 3, 2024}}</ref><ref name="ES 23">{{cite web|url=https://www.standard.co.uk/culture/music/the-most-influential-hip-hop-artists-of-all-time-drake-nicki-minaj-a3863356.html|title=50 years of hip-hop: from Nas to Drake, we pick the world-changing genre's most influential artists|work=[[Evening Standard]]|date=August 11, 2023|access-date=September 3, 2024}}</ref> ''[[Complex (magazine)|Complex]]'' said that Minaj has written bars that are "as insulting as they [are] clever", and praised her versatility as a rapper "with varied flows, ranging from sing-song to pummeling, and lines as personal as they [are] provocative", noting that "she could go as pop as she wants, but for Minaj, it all returns to rap."<ref>{{Cite web |title=The Best Rapper Alive, Every Year Since 1979 |url=https://www.complex.com/music/the-best-rapper-alive-every-year-since-1979 |access-date=January 20, 2022 |work=Complex|date=January 22, 2024}}</ref> Tara Colley of ''[[The Conversation (website)|The Conversation]]'' described Minaj as a "pre-eminent female rapper", writing that she "consistently straddled the distinct personas of gangsta boss and sexy pop siren without truly committing to either" and her "chameleonic ability" yields debate on which version of Minaj rap fans prefer.<ref name="The Conversation">{{Cite web|url=https://theconversation.com/nicki-minaj-flips-the-script-on-hip-hop-hypermasculinity-with-her-album-queen-101666|title=Nicki Minaj flips the script on hip-hop hypermasculinity with her album Queen|website=The Conversation|access-date=September 15, 2020|last=Colley|first=Tara|date=August 19, 2018 }}</ref> Writing for ''[[Esquire (magazine)|Esquire]]'', Terron Moore said that one of Minaj's traits as an artist is her "fearlessness", a staple of "Nicki the Lyricist, the venomous, schizophrenic man eater who out-pens her male competitors, and her trail of guest verses".<ref>{{cite web|url=https://www.esquire.com/entertainment/music/a27272/nicki-minaj-mad-rapper/|title=Nicki Minaj the Mad Rapper Is Back|work=[[Esquire (magazine)|Esquire]]|date=February 13, 2014|access-date=September 3, 2024}}</ref> Robby Seabrook III of ''[[XXL (magazine)|XXL]]'' included Minaj in a list of "most unique flows from rappers over the last five years", writing that she "has solidified her spot as a leader of the pack for her animated flows, inspiring many other women in hip-hop to play with their vocals. She goes from campy to bellicose, excited to eccentric, oftentimes all on one song."<ref>{{Cite web|last=Seabrook III|first=Robby|date=September 1, 2021|title=Here Are the Most Unique Flows From Rappers Over the Last Five Years|url=https://www.xxlmag.com/most-unique-flows-rappers/|access-date=September 2, 2021|website=XXL Mag|language=en}}</ref>
As a [[hip-hop]] artist,<ref name="Independent">{{cite web|url=https://www.independent.co.uk/arts-entertainment/music/features/in-the-pink-first-lady-of-hip-hop-nicki-minaj-is-a-bewigged-global-phenomenon-2331338.html|title=In the pink: First lady of hip-hop Nicki Minaj is a bewigged global phenomenon|newspaper=[[The Independent]] |last=Haramis| first=Nick| date= August 3, 2011|access-date=October 5, 2017|archive-date=September 11, 2018|archive-url=https://web.archive.org/web/20180911115835/https://www.independent.co.uk/arts-entertainment/music/features/in-the-pink-first-lady-of-hip-hop-nicki-minaj-is-a-bewigged-global-phenomenon-2331338.html|url-status=dead}}</ref><ref>{{cite news| url=https://urbanislandz.com/2021/12/15/nicki-minaj-doja-cat-makes-guinness-world-record-with-say-so/|title=Nicki Minaj, Doja Cat Makes Guinness World Record With 'Say So'| website= urbanislandz.com |date=December 15, 2021|quote=Nicki Minaj is quickly cementing herself as one of the greatest rappers of the decade as she continues to rake in accolades over her illustrious career.|access-date=January 12, 2022|archive-date=January 9, 2022|archive-url=https://web.archive.org/web/20220109205905/https://urbanislandz.com/2021/12/15/nicki-minaj-doja-cat-makes-guinness-world-record-with-say-so/|url-status=live}}</ref> Minaj's discography comprises various styles and sounds fused with the genre, such as [[Pop music|pop]] and [[contemporary R&B|R&B]].<ref name="billboardgreatest1"/><ref name="McInnes">{{cite web|url=https://sniffers.co.nz/article/60310-2|title=Nicki Minaj shares lead single from ''Pink Friday 2'', 'Last Time I Saw You'|publisher=Sniffers|first=Laura|last=McInnes|date=September 1, 2023|accessdate=November 11, 2024}}</ref><ref>{{cite magazine|url=https://www.billboard.com/lists/nicki-minajs-pink-friday-2-songs-ranked/needle-feat-drake/|title=Nicki Minaj's 'Pink Friday 2' Album: All 22 Songs Ranked|magazine=Billboard|date=December 8, 2023|accessdate=November 11, 2024}}</ref> Several of her albums have been described as genre-blending.<ref name="billboardgreatest1"/><ref>{{cite web|url=https://www.revolt.tv/article/2019-09-09/86544/9-of-nicki-minajs-most-memorable-verses|title=9 of Nicki Minaj's most memorable verses|publisher=[[Revolt (TV network)|Revolt]]|date=September 9, 2019|access-date=November 8, 2024}}</ref> Minaj occasionally lends herself to [[Electronic dance music|electronic music genres]],<ref>{{cite web|last=Rogulewski|first=Charley|title=Nicki Minaj's 'Pink Friday: Roman Reloaded' Album Track-by-Track Preview|url=http://www.theboombox.com/2012/04/02/nicki-minajs-pink-friday-roman-reloaded-album-preview/|website=The Boom Box|date=April 2, 2012 |publisher=[[AOL]]|access-date=November 3, 2012}}</ref> with ''Pink Friday'' marking her exploration of the genre, spawning the [[electropop]]-laden "[[Super Bass]]".<ref>{{cite web | url=http://www.ebony.com/entertainment-culture/opinion-nicki-minaj-out-for-pop-or-hip-hop | title=[Opinion] Nicki Minaj Out for Pop or Hip Hop? | website=Ebony | date=April 20, 2012 | access-date=March 3, 2016}}</ref><ref>{{cite web|last=Newstead|first=Al|title=Nicki Minaj : Pink Friday Review|url=http://www.beat.com.au/music/2011/01/19/nicki-minaj-pink-friday/album-nicki-minaj-pink-friday-review|website=Beat|publisher=Furst Media|access-date=November 3, 2012|archive-date=October 9, 2012|archive-url=https://web.archive.org/web/20121009034611/http://www.beat.com.au/music/2011/01/19/nicki-minaj-pink-friday/album-nicki-minaj-pink-friday-review|url-status=dead}}</ref> Also combining hip hop with electronic music, Minaj's second album, ''Pink Friday Roman Reloaded'', has several electro-rap and electro pop songs including "[[Roman Holiday (song)|Roman Holiday]]", "[[Beez in the Trap]]", and "[[Starships (song)|Starships]]".{{refn|''Roman Reloaded'' electronic songs:<ref name="rollingstone1">{{cite magazine | last =Rosen | first =Jody | title =Nicki Minaj | magazine =Rolling Stone | date =April 6, 2012 | url =https://www.rollingstone.com/music/albumreviews/pink-friday-roman-reloaded-20120406 | access-date =November 20, 2013 | archive-date =August 5, 2020 | archive-url =https://web.archive.org/web/20200805024158/https://www.rollingstone.com/music/albumreviews/pink-friday-roman-reloaded-20120406 | url-status =dead }}</ref><ref name=holidayall>{{cite web|last=Jeffries|first=David|title=Pink Friday: Roman Reloaded – Review|url=https://www.allmusic.com/album/pink-friday-roman-reloaded-mw0002290917|website=[[AllMusic]]|publisher=[[Rovi]]|access-date=November 3, 2012}}</ref><ref>{{cite web|last=Phillips|first=Demi|title=Nicki Minaj's "Pink Friday: Roman Reloaded" LP Turns 11|date=October 13, 2023|url=https://www.hotnewhiphop.com/725493-nicki-minaj-pink-friday-roman-reloaded|archive-url=https://web.archive.org/web/20251206093150/https://www.hotnewhiphop.com/725493-nicki-minaj-pink-friday-roman-reloaded|url-status=live|archive-date=December 6, 2025|work=[[HotNewHipHop]]|access-date=March 10, 2026}}</ref><ref>{{cite magazine|last=Sherman|first=Maria|url=https://www.billboard.com/music/music-news/nicki-minaj-debuts-new-single-starships-listen-506145/|title=Nicki Minaj Debuts New Single 'Starships': Listen|magazine=Billboard|publisher=Rovi|date=February 14, 2012|access-date=February 15, 2012}}</ref>}} Zoe Johnson of ''XXL'', stated in 2021 that in recent years Minaj's "beat selection has moved to refined production full of grit and hip-hop flare".<ref name="ZoeJohnsonXXLNM">{{Cite web|title=Nicki Minaj 2021 Type Beats|url=https://www.xxlmag.com/nicki-minaj-type-beat-2021/|website=XXL|date=April 16, 2021 |access-date=May 2, 2021}}</ref> Her fifth album ''Pink Friday 2'' incorporates [[Drill music|drill]], pop, [[dancehall]], [[afrobeats]], R&B, [[Jersey club]], and [[Trap music|trap]].<ref>{{cite web |url=https://pitchfork.com/reviews/albums/nicki-minaj-pink-friday-2/ |title=Pink Friday 2 |work=Pitchfork |last=Shepherd |first=Julianne Escobedo |date=December 11, 2023 |access-date=January 22, 2024 |archive-date=December 11, 2023 |archive-url=https://web.archive.org/web/20231211072703/https://pitchfork.com/reviews/albums/nicki-minaj-pink-friday-2/ |url-status=live}}</ref>
=== Alter egos ===
With her parents frequently fighting during her childhood, Minaj lived through characters she created as a means of escape. She recalled that "fantasy was my reality" and her first identity was Cookie. She then moved on to Harajuku Barbie and, later, Nicki Minaj.<ref name="NewYorkAlterego">{{cite web|url =https://nymag.com/guides/summer/2010/66786/|title= Nicki Minaj, the Rapper With a Crush on Meryl Streep|first=Lizzy|last=Goodman|access-date =July 22, 2010|date = June 20, 2010|work=[[New York (magazine)|New York]]}}</ref> In November 2010, Minaj assumed the [[alter ego]] Nicki Teresa, wearing a colorful headdress and calling herself "healer to her fans" during a visit to the Garden of Dreams Foundation at Fuse Studios in New York.<ref name="ReferenceA">{{cite web | url=http://www.rap-up.com/2010/11/18/nicki-minaj-has-healing-powers/ | title=Nicki Minaj Has Healing Powers | access-date=February 16, 2011 | date=November 18, 2010 | website=Rap-Up | archive-url=https://web.archive.org/web/20121215060022/http://www.rap-up.com/2010/11/18/nicki-minaj-has-healing-powers/ | archive-date=December 15, 2012 | url-status=dead}}</ref> She introduced another alter ego, Rosa (pronounced with an exaggerated ''R''), to commemorate her December 2010 appearance on ''[[Lopez Tonight]]''.<ref>{{cite web | url=http://www.rap-up.com/2010/12/06/nicki-minaj-introduces-new-alter-ego/ | title=Nicki Minaj Introduces New Alter Ego | access-date=February 16, 2011 | date=December 6, 2010 | website=Rap-Up | archive-url=https://web.archive.org/web/20110211111026/http://www.rap-up.com/2010/12/06/nicki-minaj-introduces-new-alter-ego/ | archive-date=February 11, 2011 | url-status=dead}}</ref>
One of Minaj's most well-known alter-egos is Roman Zolanski, whom Minaj described as a "demon", a "lunatic", and a gay boy that lives inside of her.<ref name="rap 22">{{cite web|url=https://www.rap-up.com/article/2022/08/18/nicki-minaj-super-freaky-girl-roman-remix|title=Nicki Minaj's Alter Ego Roman Returns on 'Super Freaky Girl (Remix)'|work=[[Rap-Up]]|date=August 18, 2022|access-date=September 3, 2024|archive-url=https://web.archive.org/web/20240903103003/https://www.rap-up.com/article/2022/08/18/nicki-minaj-super-freaky-girl-roman-remix|archive-date=September 3, 2024|url-status=dead}}</ref><ref name="MTV 12">{{cite web|last=Vena |first=Jocelyn |url=http://www.mtv.com/news/articles/1679156/nicki-minaj-roman-zolanski-grammys.jhtml |archive-url=https://web.archive.org/web/20120215024832/http://www.mtv.com/news/articles/1679156/nicki-minaj-roman-zolanski-grammys.jhtml |url-status=dead |archive-date=February 15, 2012 |title=Nicki Minaj's Alter Ego Roman Zolanski Makes Grammy Debut – Music, Celebrity, Artist News |publisher=MTV |date=February 13, 2012 |access-date=May 27, 2012}}</ref> On "[[Roman's Revenge]]", Minaj and [[Eminem]] collaborated as their respective alter egos Roman and [[Slim Shady]].<ref>{{cite web|url=http://www.rap-up.com/2010/10/28/nicki-minaj-gets-revenge-with-eminem/|title=Nicki Minaj Gets 'Revenge' With Eminem|last=Dawson|first=Imani A.|website=Rap-Up|access-date=November 4, 2010|archive-url=https://web.archive.org/web/20110102030246/http://www.rap-up.com/2010/10/28/nicki-minaj-gets-revenge-with-eminem/|archive-date=January 2, 2011|url-status=dead}}</ref> Minaj also introduced the character of Roman's proper British mother, Martha, in "Roman's Revenge".<ref>{{cite web | title=A Guide to the Many Personalities of Nicki Minaj| date=April 3, 2012| url=https://www.papermag.com/a-guide-to-the-many-personalities-of-nicki-minaj-1426034704.html| access-date=September 3, 2024|work=[[Paper (magazine)|Paper]]|archive-url=https://web.archive.org/web/20191227162041/https://www.papermag.com/a-guide-to-the-many-personalities-of-nicki-minaj-1426034704.html|archive-date=December 27, 2019|url-status=dead}}</ref><ref name="MTV 12"/> In December 2023, in response to a fan inquiry on Martha ever returning, Minaj confirmed that Martha had died of old age.<ref>{{cite web|url=https://meaww.com/rip-martha-nicki-minajs-post-causes-martha-stewart-panic-and-confusion-internet-relieved-to-learn-chef-is-alive|title='RIP Martha': Nicki Minaj's post causes Martha Stewart panic and confusion, Internet relieved to learn chef is alive|website=meaww|date=December 4, 2023 |access-date=December 4, 2023}}</ref> Minaj's other personas in her music include Chun-Li and Queen Sleeze.<ref name="rap 22"/>
=== Influences ===
{{multiple image
| align = right
| image1 = Lil Wayne cropped.jpg
| width1 = 130
| alt1 =
| caption1 =
| image2 = Foxy brown-03-mika.jpg
| width2 = 171
| alt2 =
| caption2 =
| image3 = Jay-Z 2011.jpg
| width3 = 164
| alt3 =
| caption3 =
| footer = Minaj called rappers [[Lil Wayne]] (left), [[Foxy Brown (rapper)|Foxy Brown]] (center), and [[Jay-Z]] (right) her three biggest influences.
| direction =
| total_width =
}}
Minaj cited [[Lil Wayne]], [[Foxy Brown (rapper)|Foxy Brown]], [[Jay-Z]], and [[Biggie]] as major influences.<ref name="NYT 17">{{Cite news|last=Gay|first=Roxane|date=October 16, 2017|title=Nicki Minaj, Always in Control|language=en-US|work=The New York Times|url=https://www.nytimes.com/2017/10/16/t-magazine/nicki-minaj.html|access-date=August 12, 2021|issn=0362-4331}}</ref><ref>{{cite magazine|url=https://ew.com/music/2018/08/10/nicki-minaj-queen-best-lyrics/|title=The 15 best lyrics from Nicki Minaj's Queen|magazine=Entertainment Weekly|date=May 16, 2022|access-date=September 3, 2024}}</ref> She said, "I can't even imagine my career, my creative spirit without Wayne".<ref name="IM 21">{{Cite web|date=March 25, 2011|title=Nicki Minaj on Lil Wayne|url=https://www.interviewmagazine.com/music/nicki-minaj-on-lil-wayne|access-date=August 12, 2021|website=Interview|language=en-US}}</ref><ref>{{Cite magazine|title=Lil Wayne: 'Nicki Minaj is an icon, a boss and a role model'|url=https://ew.com/article/2016/04/21/lil-wayne-nicki-minaj/|access-date=August 12, 2021|magazine=Entertainment Weekly|archive-date=August 12, 2021|archive-url=https://web.archive.org/web/20210812023330/https://ew.com/article/2016/04/21/lil-wayne-nicki-minaj/|url-status=dead}}</ref> On Foxy Brown and Jay-Z, Minaj stated: "I really loved [Foxy] as a female rapper. I was really interested in her mind and her aura, [and] I was really, really into Jay-Z. Me and my friends in high school, we were reciting all of the Jay lyrics."<ref>{{cite web |url=http://www.rap-up.com/2012/06/27/nicki-minaj-talks-arena-tour-hints-at-big-surprise/#more-128320 |title=Nicki Minaj Talks Arena Tour, Hints at 'Big Surprise' |website=Rap-Up |date=June 27, 2012 |access-date=August 3, 2012 |archive-url=https://web.archive.org/web/20180903082029/https://www.rap-up.com/2012/06/27/nicki-minaj-talks-arena-tour-hints-at-big-surprise/#more-128320 |archive-date=September 3, 2018 |url-status=dead}}</ref> She deemed Foxy Brown "the most influential female rapper" for her.<ref>{{cite web|title=Nicki Minaj calls Foxy Brown 'The Most Influential Female Rapper'|url=http://www.mtv.com/news/articles/1691906/nicki-minaj-foxy-brown-influence.jhtml|archive-url=https://web.archive.org/web/20120819235131/http://www.mtv.com/news/articles/1691906/nicki-minaj-foxy-brown-influence.jhtml|url-status=dead|archive-date=August 19, 2012|publisher=MTV News. Viacom|access-date=August 23, 2012}}</ref>
Minaj also cited [[Slick Rick]], [[Doug E. Fresh]], [[Lauryn Hill]], [[Salt-N-Pepa]], [[Snoop Dogg]], [[Eazy-E]], and [[Tupac]] as influences.<ref name="XXL 17">{{cite web|url=https://20th.xxlmag.com/nicki-minaj|title=Nicki Minaj Discusses her Unique Role in Hip Hop|work=[[XXL (magazine)|XXL]]|date=2017|archive-url= https://web.archive.org/web/20241204135505/https://20th.xxlmag.com/nicki-minaj|archive-date=December 4, 2024|url-status=live}}</ref><ref>{{Cite web|title=Excuse me while I have an outer body experience. Shaking, crying, a MESS!!!! 😩 this lady is the reason. Omg. The QUEEN. Goddess! The epitome! The bar! The *faints*. Ms Lauryn Hill told me to keep "spittin dat fire". Is this real life!????|url=https://www.instagram.com/p/BLnMsGvj9Sf|access-date=August 9, 2021|author=Nicki Minaj|publisher=[[Instagram]]|archive-url=https://web.archive.org/web/20220914201845/https://www.instagram.com/p/BLnMsGvj9Sf/|archive-date=September 14, 2022|url-status=bot: unknown}}</ref><ref>{{Cite web|title=I quoted her in my HS Yearbook!!!!!|url=https://www.instagram.com/p/BLnNPcujN93/|access-date=August 9, 2021|author=Nicki Minaj|publisher=[[Instagram]]|archive-url=https://web.archive.org/web/20230206222618/https://www.instagram.com/p/BLnNPcujN93/|archive-date=February 6, 2023|url-status=bot: unknown}}</ref> Other artists she mentioned as inspirations included [[R&B]] singer [[Monica (entertainer)|Monica]],<ref>{{cite web|url=https://www.iheart.com/content/2024-02-09-monica-will-join-nicki-minaj-on-her-pink-friday-2-tour/|title=Monica Will Join Nicki Minaj On Her 'Pink Friday 2 Tour'|publisher=[[iHeart]]|date=February 9, 2024|access-date=October 19, 2024}}</ref> [[Beyoncé]],<ref>{{Cite web |title=Nicki Minaj Felt 'very Emotional' While Performing With Beyonce In Paris|url=http://www.mtv.com/news/1932289/nicki-minaj-on-performing-beyonce-paris/|date=September 16, 2014|access-date=April 5, 2023 |website=MTV|archive-url=https://web.archive.org/web/20170214194436/http://www.mtv.com/news/1932289/nicki-minaj-on-performing-beyonce-paris/|archive-date=February 14, 2017|url-status=dead}}</ref> [[Whitney Houston]],<ref>{{Cite magazine |date=15 December 2023 |title=Nicki Minaj Talks About Her 'Connection' to Whitney Houston, Dishes on Pink Friday 2 World Tour Details |url=https://www.billboard.com/music/music-news/nicki-minaj-whitney-houston-connection-pink-friday-2-world-tour-details-1235559141/ |magazine=Billboard}}</ref> [[Madonna]],<ref name="Minaj two">{{Cite news|date=April 12, 2012|title=Nicki Minaj: 'I'm inspired by Madonna'|work=[[NME]]|url=https://www.nme.com/news/music/nicki-minaj-92-1281740|access-date=April 11, 2013}}</ref> and [[Enya]].<ref>{{cite web|last=Lee|first=Bill|date=June 11, 2014|title=Nicki Minaj: 'New album was inspired by Enya'|url=https://www.digitalspy.com/music/a608581/nicki-minaj-new-album-was-inspired-by-enya/|access-date=February 1, 2019|website=Digital Spy}}</ref> Other female rappers she listened to included [[Eve (rapper)|Eve]], [[MC Lyte]], [[Left Eye]], [[Lil' Kim]], [[Remy Ma]], and [[Missy Elliott]].<ref>{{Cite web |title=Nicki Minaj Gives Props to Lil' Kim, Foxy Brown, and Remy Ma |url=https://www.rap-up.com/2013/05/29/nicki-minaj-gives-props-to-lil-kim-foxy-brown-and-remy-ma/ |access-date=March 27, 2023 |website=Rap-Up |archive-url=https://web.archive.org/web/20230327060832/https://www.rap-up.com/2013/05/29/nicki-minaj-gives-props-to-lil-kim-foxy-brown-and-remy-ma/ |archive-date=March 27, 2023 |url-status=dead}}</ref><ref>{{Cite web |last=Macias |first=Ernesto |date=October 18, 2022 |title=Nicki Minaj, in Conversation With Jada Pinkett Smith |url=https://www.interviewmagazine.com/music/nicki-minaj-in-conversation-with-jada-pinkett-smith |access-date=March 26, 2023 |website=Interview}}</ref>
Minaj called [[Betsey Johnson]] a fashion inspiration: "[Betsey] is a free spirit. When I met her the other day, I felt like I knew her for my whole life. ... She's amazingly talented and I've been wearing her clothes forever."<ref>{{cite web | last = Garibaldi | first = Christina | title = Nicki Minaj Is Betsey Johnson's 'Most Favorite Girl' |publisher=MTV News. Viacom| date =September 13, 2011 | url =http://www.mtv.com/news/articles/1670652/nicki-minaj-betsey-johnson.jhtml | archive-url =https://web.archive.org/web/20120103032503/http://www.mtv.com/news/articles/1670652/nicki-minaj-betsey-johnson.jhtml | url-status =dead | archive-date =January 3, 2012 | access-date = November 20, 2013}}</ref> Minaj also expressed appreciation for [[Cyndi Lauper]]'s style and how her videos inspired her as a teenager: "When I first went to get my hair colored, I was about 14 and I wanted blonde highlights. ... I've always been experimenting. Cyndi Lauper's videos – that's what intrigued me."<ref>{{cite web | author =Wenn | title =Nicki Minaj – Nicki Minaj's Style Inspired By Cyndi Lauper |publisher=Contact Music| date =February 17, 2012 | url =http://www.contactmusic.com/news/nicki-minajs-style-inspired-by-cyndi-lauper_1295675 | access-date = November 20, 2013}}</ref> Other fashion influences include [[Janet Jackson]],<ref name="style">{{Cite web |date=January 9, 2014 |title=Picks and Pans Review: Nicki Minaj's Top 5 Style Idols : People.com |url=http://www.people.com/people/article/0,,20445758,00.html |access-date=March 26, 2023 |archive-url=https://web.archive.org/web/20140109005029/http://www.people.com/people/article/0,,20445758,00.html |archive-date=January 9, 2014 }}</ref> [[Salt-N-Pepa]],<ref name="style"/> [[Grace Jones]],<ref>{{Cite web |date=May 19, 2019 |title=Imitation Situation: 8 Stars Inspired By Grace Jones' IDGAF Style |url=https://globalgrind.com/playlist/imitation-situation-8-stars-inspired-by-grace-jones-idgaf-style/ |access-date=March 29, 2023 |website=Global Grind |archive-url=https://web.archive.org/web/20200414193434/https://globalgrind.com/playlist/imitation-situation-8-stars-inspired-by-grace-jones-idgaf-style/|archive-date=April 14, 2020|url-status=dead}}</ref> Lil' Kim,<ref>{{Cite web |title=Nicki Minaj says she and Lil Kim should have been on US Vogue |url=https://www.revolt.tv/article/2022-03-09/156233/nicki-minaj-says-she-lil-kim-should-have-been-on-us-vogue|access-date=March 26, 2023 |website=[[Revolt (TV network)|Revolt]] |date=March 9, 2022}}</ref> and [[Marilyn Monroe]].<ref>{{Cite magazine |last=Hajdu |first=David |date=August 5, 2012 |title=What Nicki Minaj Learned From Marilyn Monroe |magazine=The New Republic |url=https://newrepublic.com/article/105800/what-nicki-minaj-learned-marilyn-monroe |access-date=April 5, 2023 |issn=0028-6583}}</ref> She also mentioned that actress [[Jada Pinkett Smith]] was one of her role models.<ref>{{cite web|last=Ziegbe |first=Mawuse |url=http://www.mtv.com/news/articles/1642470/nicki-minaj-dedicates-bet-award-lil-wayne.jhtml |archive-url=https://web.archive.org/web/20110217151537/http://www.mtv.com/news/articles/1642470/nicki-minaj-dedicates-bet-award-lil-wayne.jhtml |url-status=dead |archive-date=February 17, 2011 |title=Nicki Minaj Dedicates BET Award To Lil Wayne, Shouts Out Female Rappers – Music, Celebrity, Artist News |publisher=MTV |date=June 27, 2010 |access-date=August 3, 2012}}</ref>
== Legacy ==
Various publications have referred to Minaj as the "[[Honorific nicknames in popular music#M|Queen of Rap]]" and the "Queen of Hip Hop".<ref>Minaj cited as the "Queen of Rap":
* {{Cite web |date=August 10, 2018 |title=GQ Editors Pick the Best Nicki Minaj Verses |url=https://www.gq.com/story/these-are-the-best-nicki-minaj-verses |access-date=October 19, 2021 |website=GQ |language=en-US }}
* {{Cite magazine |date=February 4, 2022 |title=Nicki Minaj x Lil Baby, Mitski & More: What's Your Favorite Music Release of the Week? Vote! |url=https://www.billboard.com/music/pop/new-music-release-poll-nicki-minaj-lil-baby-mitski-1235027444/ |language=en |access-date=February 7, 2022 |magazine=Billboard }}
* {{Cite web |title=Nicki Minaj backs Texas councilman who gave her a birthday shoutout |url=https://www.nbcnews.com/nbc-out/out-pop-culture/nicki-minaj-backs-texas-councilman-gave-birthday-shoutout-rcna8569 |access-date=January 7, 2022 |publisher=NBC News |date=December 13, 2021 |language=en }}
* {{Cite magazine |last=Lang |first=Cady |date=March 14, 2017 |title=Watch Nicki Minaj Flawlessly Lip Sync to Celine Dion |url=https://time.com/4701402/nicki-minaj-celine-dion-lip-sync/ |magazine=Time |language=en |access-date=November 11, 2021 |archive-date=November 11, 2021 |archive-url=https://web.archive.org/web/20211111172255/https://time.com/4701402/nicki-minaj-celine-dion-lip-sync/ |url-status=dead }}
* {{cite magazine|title=Nicki Minaj Flaunts Her Best Adele, Gets Vulnerable, and Plays Clarinet on 'Carpool Karaoke'|magazine=[[Rolling Stone]]|url=https://www.rollingstone.com/music/music-news/nicki-minaj-james-corden-carpool-karaoke-1333825/|date=April 7, 2022|access-date=September 13, 2024|archive-url=https://web.archive.org/web/20230505025219/https://www.rollingstone.com/music/music-news/nicki-minaj-james-corden-carpool-karaoke-1333825/|archive-date=May 5, 2023|url-status=live}}
* {{cite web|title=Nicki Minaj's Tour Returns To The U.S., Fueling Her Album's Rise On The Charts|work=[[Forbes]]|url=https://www.forbes.com/sites/hughmcintyre/2024/09/11/nicki-minajs-tour-returns-to-the-us-fueling-her-albums-rise-on-the-charts/|archive-url=https://web.archive.org/web/20240911140658/https://www.forbes.com/sites/hughmcintyre/2024/09/11/nicki-minajs-tour-returns-to-the-us-fueling-her-albums-rise-on-the-charts/|archive-date=September 11, 2024|date=September 11, 2024|access-date=September 13, 2024}}
* {{cite web|title=Nicki Minaj, in Las Vegas tour stop, solidifies reign as the Queen of Rap|work=[[Las Vegas Sun]]|url=https://lasvegassun.com/news/2024/mar/13/nicki-minaj-in-las-vegas-tour-stop-solidifies-reig/|date=March 13, 2024|access-date=September 13, 2024|archive-url=https://web.archive.org/web/20240914042909/https://lasvegassun.com/news/2024/mar/13/nicki-minaj-in-las-vegas-tour-stop-solidifies-reig/|archive-date=September 14, 2024|url-status=live}}
* {{Cite web |last=Mappoura |first=Lia |date=February 15, 2022 |title=This throwback picture of Nicki Minaj is proof that she ages backwards |url=https://www.cosmopolitan.com/uk/beauty-hair/celebrity-hair-makeup/a39088801/nicki-minaj-throwback-picture/ |access-date=February 17, 2022 |website=[[Cosmopolitan (magazine)|Cosmopolitan]] }}
* {{cite web|title=Nicki Minaj: See her award-winning rap career in pictures|work=[[USA Today]]|url=https://www.usatoday.com/picture-gallery/life/music/2018/08/22/nicki-minaj-queen-rap-royalty/1061645002/|date=August 22, 2018|access-date=September 13, 2024|archive-url=https://web.archive.org/web/20230424043855/https://www.usatoday.com/picture-gallery/life/music/2018/08/22/nicki-minaj-queen-rap-royalty/1061645002/|archive-date=April 24, 2023|url-status=live}}
* {{Cite web |last=Wang |first=Steffanee |title=Nicki Minaj & Lil Baby Pull Of A Heist On 'Do We Have A Problem |url=https://www.nylon.com/entertainment/nicki-minaj-lil-baby-do-we-have-a-problem-video |access-date=February 17, 2022 |website=[[Nylon (magazine)|Nylon]] |date=February 4, 2022 |language=en }}
* {{Cite web |date=September 16, 2021 |title=Nicki Minaj: every album ranked and rated |url=https://www.nme.com/features/nicki-minaj-every-album-ranked-and-rated-3047363 |access-date=November 1, 2021 |website=NME }}
* {{cite web|title=Nicki Minaj, the "Queen of Rap," bringing world tour to Baltimore in April|work=[[CBS News]]|url=https://www.cbsnews.com/baltimore/news/nicki-minaj-the-queen-of-rap-to-perform-in-baltimore-in-april/|date=December 11, 2023|access-date=September 13, 2024|archive-url=https://web.archive.org/web/20231212091432/https://www.cbsnews.com/baltimore/news/nicki-minaj-the-queen-of-rap-to-perform-in-baltimore-in-april/|archive-date=December 12, 2023|url-status=live}}
* {{Cite web |title=Nicki Minaj Pulled Out Of The 2021 VMAs For A Mysterious Reason |url=https://www.bustle.com/entertainment/why-nicki-minaj-wont-perform-at-2021-mtv-vmas |access-date=December 1, 2021 |website=Bustle |date=September 12, 2021 }}
* {{cite web|url=https://thesource.com/2024/05/15/nicki-minaj-wraps-up-pink-friday-2-tour-with-record-breaking-success/|title=Nicki Minaj Wraps Up 'Pink Friday 2 Tour with Record-Breaking Success|date=May 15, 2024|access-date=October 11, 2024|work=[[The Source (magazine)|The Source]]}}</ref><ref>Minaj cited as the "Queen of Hip Hop":
* {{cite magazine|title=Nicki Minaj: The New Queen of Hip-Hop|magazine=[[Rolling Stone]]|url=https://www.rollingstone.com/music/music-news/nicki-minaj-the-new-queen-of-hip-hop-103304/|archive-url=https://web.archive.org/web/20230407214739/https://www.rollingstone.com/music/music-news/nicki-minaj-the-new-queen-of-hip-hop-103304/|archive-date=April 7, 2023|date=December 9, 2010|access-date=September 13, 2024|url-status=live}}
* {{Cite news|title=From Queens to Queen: Nicki Minaj, 'Pink Friday'|language=en-US|newspaper=The Washington Post|url=https://www.washingtonpost.com/express/wp/2010/11/22/nicki-minaj-pink-friday-review/|access-date=November 11, 2021|issn=0190-8286}}
* {{cite web|first=Sowmya|last=Krishnamurthy|title=Nicki Minaj Is The 21st Century's Insatiable Hip-Hop Monarch|url=https://www.npr.org/2018/10/03/651761719/nicki-minaj-is-the-21st-centurys-insatiable-hip-hop-monarch|date=October 3, 2018|access-date=June 9, 2020|publisher=NPR}}
* {{Cite web|last=Fitzgerald|first=Trent|title=Nicki Minaj's Most Essential Songs You Need to Hear|url=https://www.xxlmag.com/nicki-minaj-essential-songs/|access-date=November 3, 2021|website=XXL Mag|date=December 8, 2020}}
* {{Cite news|last1=Best|first1=Tamara|last2=Chow|first2=Andrew R.|date=August 30, 2015|title=VMAs 2015: Moments to Watch|language=en-US|work=The New York Times|url=https://www.nytimes.com/2015/08/30/arts/music/2015-mtv-vmas.html|access-date=November 11, 2021|issn=0362-4331}}
* {{Cite web|title=In the pink: First lady of hip-hop Nicki Minaj is a bewigged global phenomenon|url=https://www.independent.co.uk/arts-entertainment/music/features/in-the-pink-first-lady-of-hip-hop-nicki-minaj-is-a-bewigged-global-phenomenon-2331338.html|access-date=September 13, 2024|work=[[The Independent]]|date=August 4, 2011 |archive-url=https://web.archive.org/web/20180911115835/https://www.independent.co.uk/arts-entertainment/music/features/in-the-pink-first-lady-of-hip-hop-nicki-minaj-is-a-bewigged-global-phenomenon-2331338.html|archive-date=September 11, 2018|url-status=live}}
* {{Cite magazine|title=Nicki Minaj's 13 Most Priceless Music Moments|url=https://www.billboard.com/articles/events/hot-100-festival/6633571/nicki-minaj-hot-100-festival-timeline|access-date=November 3, 2021|magazine=Billboard|date=July 16, 2015}}
* {{cite web|url=https://20th.xxlmag.com/nicki-minaj|title=Nicki Minaj Discusses her Unique Role in Hip Hop|work=[[XXL (magazine)|XXL]]|date=2017|archive-url= https://web.archive.org/web/20241204135505/https://20th.xxlmag.com/nicki-minaj|archive-date=December 4, 2024|url-status=live}}</ref> She is also regarded as one of the most influential rappers of all time.<ref>Sources on Minaj's influence:
* {{Cite news |last=Caramanica |first=Jon |date=March 30, 2012 |title=A Singular Influence |language=en-US |work=[[The New York Times]] |url=https://www.nytimes.com/2012/04/01/arts/music/nicki-minaj-is-the-influential-leader-of-hip-hop.html |access-date=March 30, 2022 |issn=0362-4331 |quote=This hard and complex work has paid off: when she releases her second album, 'Pink Friday: Roman Reloaded', this week, it will be as the most influential female rapper of all time.}}
* {{Cite news |last=Embley |first=Jochan |date=June 16, 2020 |title=50 years of hip-hop: from Nas to Drake, we pick the world-changing genre's most influential artists|website=[[Evening Standard]] |url=https://www.standard.co.uk/culture/music/the-most-influential-hip-hop-artists-of-all-time-a3863356.html |access-date=March 30, 2022 |quote=For many, it was an searing introduction to [Minaj] who would dominate pop music, fashion and social politics for much of this decade, repeatedly breaking down barriers for female artists in the scene.}}
* {{Cite news |last=Harrington |first=Jim |date=August 6, 2020 |title=Top 10 female rappers of all time: Did your favorite make our list?|website=[[The Mercury News]] |url=https://www.mercurynews.com/2020/08/06/top-10-female-rappers-of-all-time-did-your-favorite-make-our-list |access-date=March 30, 2022 |quote=[Minaj] has breathtaking talent and is able to accomplish things on the microphone that are simply spellbinding.}}
* {{Cite magazine |last=Ramirez |first=Erika |date=March 31, 2014 |title=Ladies First: 31 Female Rappers Who Changed Hip-Hop |url=https://www.billboard.com/music/rb-hip-hop/ladies-first-31-female-rappers-who-changed-hip-hop-5923011/ |magazine=[[Billboard (magazine)|Billboard]] |access-date=March 30, 2022 |quote=Many female rhymers, from Queen Latifah to Nicki Minaj, have destroyed the proverbial glass ceiling to become legends in their own right. In celebration of women's history month, we're spotlighting 31 of the greatest female rappers of all time.}}
* {{Cite web|url=https://www.complex.com/music/2020/11/nicki-minaj-pink-friday-10-year-anniversary|title=The Lasting Legacy of Nicki Minaj|access-date=May 1, 2021|website=[[Complex (magazine)|Complex]]|date=November 22, 2020}}
* {{Cite news |last=Staples |first=Brent |date=July 7, 2012 |title=Nicki Minaj Crashes Hip-Hop's Boys Club|work=[[The New York Times]] |url=https://www.nytimes.com/2012/07/08/opinion/sunday/nicki-minaj-crashes-hip-hops-boys-club.html |access-date=March 30, 2022 |issn=0362-4331 |quote=Two albums later ... [Minaj] is already being hailed, with some justification, as the most influential female rapper of all time.}}
* {{Cite news |date=November 29, 2020 |title=Saweetie, City Girls and the Female Rapper Renaissance |language=en-US |work=[[The New York Times]] |url=https://www.nytimes.com/2020/11/29/arts/music/female-rappers.html |access-date=March 30, 2022 |issn=0362-4331 |quote=Now, finally, things appear to be evolving in a more progressive direction. Nicki Minaj has been one of the most influential rappers of the last decade.}}
* {{Cite web |last=Inman |first=Demicia |date=November 24, 2020 |title=10 years after 'Pink Friday', a look at Nicki Minaj's enduring Legacy |url=https://www.nylon.com/entertainment/nicki-minaj-pink-friday |access-date=February 15, 2021 |website=[[Nylon (magazine)|Nylon]] |quote=In 2020, her influence stands as one of the most successful rappers of the millennium, regardless of gender identity – which only confirms her power. She battled misogyny and industry bias against Black women to carve her own identity and sound.}}
* {{cite web|url=https://www.thefader.com/2017/10/16/nicki-minaj-t-magazine|title=Nicki Minaj is one of the greats|work=[[The Fader]]|date=October 16, 2017|access-date=October 11, 2024|quote=For her lyricism, her style, and her influence on rappers today, Minaj rightfully deserves to be named one of the greats.}}
* {{cite web|url=https://thesource.com/2024/01/22/nicki-minaj-to-headline-afro-nation-portugal-2024-joined-by-stellar-lineup/|title=Nicki Minaj to Headline Afro Nation Portugal 2024, Joined by Stellar Lineup|work=[[The Source (magazine)|The Source]]|date=January 22, 2024|access-date=October 11, 2024|quote=As one of the most influential figures in the global music scene, Nicki Minaj has consistently redefined US rap/hip-hop since her debut album.}}</ref> In 2012, Jon Caramanica of ''[[The New York Times]]'' called Minaj "the most influential female rapper of all time", and in 2015 Vanessa Grigoriadis from [[The New York Times Magazine|its magazine]] called her "the world's biggest female rap superstar".<ref name="NYT20120330" /><ref name="NYT123">{{Cite news|last=Grigoriadis|first=Vanessa|date=October 7, 2015|title=The Passion of Nicki Minaj|language=en-US|work=The New York Times|url=https://www.nytimes.com/2015/10/11/magazine/the-passion-of-nicki-minaj.html|access-date=November 12, 2021|issn=0362-4331}}</ref> In 2019, ''[[Complex (magazine)|Complex]]'' ranked her eighth on their list of best rappers of the 2010s, the only female rapper on the list, calling Minaj "the most important female rapper of this decade—and quite possibly of all time", and a "trendsetter" who "continuously redefin[es] what it means to be a crossover rapper."<ref>{{Cite web |last1=Skelton |first1=Eric |last2=Diaz |first2=Angel |last3=Tharpe |first3=Frazier |last4=Ortiz |first4=Edwin |last5=McKinney |first5=Jessica |last6=Thompson |first6=Paul |last7=Weiss |first7=Jeff |date=November 18, 2019 |title=The Best Rappers of the 2010s |url=https://www.complex.com/music/best-rappers-of-the-2010s/ |access-date=June 9, 2020 |website=Complex}}</ref> Minaj was ranked as the tenth-greatest rapper and greatest female rapper of all time on ''Billboard'' and ''[[Vibe (magazine)|Vibe]]''<nowiki/>'s list of the 50 greatest rappers (2023).<ref name="billboardgreatest1">{{Cite magazine |last1=Lamarre |first1=Carl |last2=Mitchell |first2=Gail |last3=Murphy |first3=Keith |last4=Saponara |first4=Michael |last5=Thomas |first5=Datwon |last6=Elibert |first6=Mark |last7=Diep |first7=Eric |last8=III |first8=William E. Ketchum |last9=Mamo |first9=Heran |date=February 8, 2023 |title=50 Greatest Rappers of All Time |url=https://www.billboard.com/lists/best-rappers-all-time/ |access-date=March 3, 2023 |magazine=Billboard}}</ref> In 2024, ''Billboard'' stated that while "hip-hop has continued to cycle through rappers looking to knock Minaj off the throne", she "remains at the apex of the rap mountain 15 years after the release of her breakout mixtape, ''Beam Me Up Scotty''".<ref name="bbhottestfemalerappers2024">{{Cite magazine |last1=Lamarre |first1=Carl |last2=Mitchell |first2=Gail |last3=Scott |first3=Damien |last4=Mamo |first4=Heran |last5=Unterberger |first5=Andrew |last6=Denis |first6=Kyle |last7=Saponara |first7=Michael |last8=Diaz |first8=Angel |date=June 5, 2024 |title=The 10 Hottest Female Rappers Right Now |url=https://www.billboard.com/lists/female-rappers-best-hip-hop/1-nicki-minaj-2/ |access-date=June 9, 2024 |magazine=Billboard}}</ref> ''[[The Source (magazine)|The Source]]'' described Minaj as "one of the most influential figures in the global music scene", noting that she "has consistently redefined US rap/hip-hop since her debut album ''Pink Friday''".<ref name="source jan">{{cite web|url=https://thesource.com/2024/01/22/nicki-minaj-to-headline-afro-nation-portugal-2024-joined-by-stellar-lineup/|title=Nicki Minaj to Headline Afro Nation Portugal 2024, Joined by Stellar Lineup|work=[[The Source (magazine)|The Source]]|date=January 22, 2024|access-date=October 11, 2024}}</ref>
Minaj's work and career has been deemed groundbreaking by multiple publications.<ref>{{cite web|url=https://www.forbes.com/sites/marisadellatto/2022/08/22/nicki-minaj-earns-3rd-career-number-one-hit-with-super-freaky-girl/|title=Nicki Minaj Earns 3rd Career Number-One Hit With 'Super Freaky Girl'|work=Forbes|date=August 22, 2022|access-date=October 11, 2024}}</ref><ref name="ES 18"/><ref name="source jan"/><ref>{{cite web|url=https://www.jsonline.com/story/entertainment/music/2023/12/11/nicki-minaj-announces-pink-friday-2-tour-including-milwaukee-show-fiserv-forum-2024/71880599007/|title=Nicki Minaj announces dates for 'Pink Friday 2 World Tour,' including Milwaukee concert|date=December 11, 2023|access-date=October 11, 2024|work=[[Milwaukee Journal Sentinel]]}}</ref> In ''[[Evening Standard]]'', Jochan Embley named her one of the most influential rap artists of all time, writing that she "repeatedly [broke] down barriers for female artists in the scene" and "everything she does is bold, fearless and distinct – whether that be her eye-popping stage attire or her expertly delivered lyrics".<ref name="ES 18"/><ref name="ES 23"/> [[Sowmya Krishnamurthy]] of ''[[NPR Music]]'' said that "in a genre where men dominate the uppermost echelon", Minaj "indelibly changed the landscape for artists in hip-hop for the past decade, showing a complex visage: the ferocious emcee who's just as comfortable being the girl-next-door, glammed-up Barbie doll or rambunctious alter-ego Roman".<ref name="NPR20181003">{{cite web|first=Sowmya|last=Krishnamurthy|title=Nicki Minaj Is The 21st Century's Insatiable Hip-Hop Monarch|url=https://www.npr.org/2018/10/03/651761719/nicki-minaj-is-the-21st-centurys-insatiable-hip-hop-monarch|date=October 3, 2018|access-date=June 9, 2020|publisher=NPR}}</ref> In 2020, Nick Soulsby of ''[[PopMatters]]'' called her the "best female rapper and the best rapper of the past ten years – no gender preposition required."<ref name="PM 20">{{Cite web|last=Soulsby|first=Nick|date=May 11, 2020|title=Nicki Minaj Is the Greatest Rapper of the Decade Bar None, PopMatters|url=https://www.popmatters.com/nicki-minaj-greatest-rapper-decade-2645966150.html|access-date=January 7, 2022|website=PopMatters|language=en-US}}</ref> ''Billboard'' said she "redefined success for women in hip-hop" in the decade.<ref name="BB change"/> [[MTV (Latin American TV channel)|MTV Latin America]] named her as one of "The Most Influential Women in the History of Music", being the only rapper on the list.<ref>{{Cite web |title=Las mujeres más influyentes de la historia de la música |url=https://www.mtvla.com/fotogaleria/6ii0jo/las-mujeres-mas-influyentes-de-la-historia-de-la-musica/8s38hx |access-date=September 28, 2024 |publisher=[[MTV (Latin American TV channel)|MTV]] |language=es |archive-date=April 18, 2023 |archive-url=https://web.archive.org/web/20230418070737/https://www.mtvla.com/fotogaleria/6ii0jo/las-mujeres-mas-influyentes-de-la-historia-de-la-musica/8s38hx |url-status=live}}</ref> In 2024, ''Billboard'' ranked Minaj at number 13 on its "[[Billboard's Greatest Pop Stars of the 21st Century|Greatest Pop Stars of the 21st Century]]" list, with Kyle Denis writing she "isn't just the greatest female rapper [...] she's also one of the key architects of how pop music sounds, how pop fandom functions and how pop stars are perceived".<ref name="greatestpopstars">{{Cite magazine |last=Denis |first=Kyle |date=October 1, 2024 |title=''Billboard''{{'}}s Greatest Pop Stars of the 21st Century: No. 13 — Nicki Minaj |url=https://www.billboard.com/music/pop/nicki-minaj-greatest-pop-stars-21st-century-1235789844/ |access-date=October 8, 2024 |magazine=Billboard}}</ref> Her success as a female rapper "has been a landmark step towards gender equality in the rap industry", ''[[Bustle (magazine)|Bustle]]'' remarked.<ref>{{cite magazine|url=https://www.bustle.com/articles/67663-kanye-wests-lecture-at-oxford-brings-up-gender-disparity-in-hip-hop-but-are-his-words-about|title=Was Kanye West Being Sexist Regarding Nicki Minaj?|magazine=[[Bustle (magazine)|Bustle]]|date=March 3, 2015|access-date=October 11, 2024|first=Chelsea|last=Mize}}</ref> ''[[Nylon (magazine)|Nylon]]'' writer Demicia Inman said her impact "stands as one of the most successful rappers of the millennium", while she "was required not just to rap well beyond her male peers, but also to be likable. In rejecting respectability politics, [Minaj] in-turn fueled a landmark career", during which she has "battled misogyny and industry bias against black women to carve her own identity and sound" and "highlighted and helped shift double standards".<ref name="NylonMagMinaj">{{Cite magazine|title=10 Years After 'Pink Friday', A Look At Nicki Minaj's Enduring Legacy|url=https://nylon.com/entertainment/nicki-minaj-pink-friday|access-date=February 15, 2021|magazine=[[Nylon (magazine)|Nylon]]|date=November 23, 2020}}</ref>
''The New York Times''{{'}}s Caramanica wrote that Minaj became "a nimble, evocative rapper, an intricate lyricist, a thoughtful singer, a risky performer", and "invented new personae". "More than any other rapper in the mainstream, she pushed hard against expectations", becoming "her own watermark".<ref name="NYT20120330"/> NPR{{'}}s Erik Nielson said in 2014 that Minaj's "success over the last decade has stood as an exception to the unwritten rule that women rappers no longer have a place among elite artists".<ref>{{Cite news|url=https://www.npr.org/sections/codeswitch/2014/03/04/285718351/where-did-all-the-female-rappers-go|title=Where Did All The Female Rappers Go?|access-date=March 16, 2021|publisher=NPR|date=March 4, 2014|last1=Nielson|first1=Erik}}</ref> ''Complex'' stated that "from her bold outfits to her multi-colored wigs, Minaj oozed confidence that inspired others who were watching closely".<ref name="ComplexNM1">{{Cite web|url=https://www.complex.com/music/a/dayna-haffenden/nicki-minaj-pink-friday-10-year-anniversary|title=The Lasting Legacy of Nicki Minaj's 'Pink Friday'|access-date=May 1, 2021|website=Complex|date=November 22, 2020}}</ref> ''[[Glamour (magazine)|Glamour]]'' included Minaj in its list of 104 women who defined 2010s pop culture.<ref>{{Cite magazine|date=December 20, 2019|title=104 Women Who Defined the Decade in Pop Culture|url=https://www.glamour.com/story/women-who-defined-the-decade-in-pop-culture|access-date=November 21, 2021|magazine=[[Glamour (magazine)|Glamour]]}}</ref> ''[[XXL (magazine)|XXL]]''{{'}}s Zoe Johnson called her "one of the most versatile MCs to bless a mic", adding that she "helped birth a new generation of rappers that mimic her style and revamp her legendary bars. She's even helped pave the way for a select group of producers looking to create Nicki Minaj-type beats with dope rappers in mind."<ref name="ZoeJohnsonXXLNM" /> Writing for ''The New York Times'', Roxane Gay said that Minaj "broadened the definition of hip-hop, making it more joyful, energetic and robust".<ref name="NYT 17"/> In their 20th anniversary cover story, ''XXL'' stated: "the lyrical giant has become one of the key figures in recent years to help bring the culture to new heights. [Minaj's] work has been outstanding, her contributions countless and the pinkprint she is leaving behind as she goes will be a new guide for many artists to follow."<ref name="XXL 17"/>
''The Independent'' described Minaj's rise to superstardom as "almost without precedent".<ref name="Independent"/> ''Billboard'' credited Minaj for bringing female rap back to the mainstream in the US,<ref>{{cite magazine |date=February 22, 2012 |title=29 Black Music Milestones: Nicki Minaj Puts Female Rap Back On the Map |url=https://www.billboard.com/articles/news/504502/29-black-music-milestones-nicki-minaj-puts-female-rap-back-on-the-map |access-date=April 30, 2020 |magazine=Billboard}}</ref> and ranked her as the top female rapper on its decade-end chart of the most successful artists of the 2010s.<ref>{{Cite magazine |date=October 31, 2019 |title=Top Artists |url=https://www.billboard.com/charts/decade-end/top-artists/ |access-date=November 3, 2022 |magazine=Billboard |archive-date=March 26, 2023 |archive-url=https://web.archive.org/web/20230326040714/https://www.billboard.com/charts/decade-end/top-artists/ |url-status=live}}</ref><ref name="billboardgreatest1"/> With over 300 features, the magazine called Minaj a "rap and pop icon", adding that "she's been one of popular music's most reliable guest performers, notching dozens of chart hits as a supporting presence on other artists' singles".<ref>{{Cite magazine |date=May 15, 2020 |title=Nicki Minaj's 20 Best Features |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-best-features-top-20-9378020/ |access-date=August 21, 2020 |magazine=Billboard}}</ref> ''Complex'' said Minaj was able "take a simple song and turn it into a smash hit just because she's featured on it".<ref>{{Cite magazine|url=https://www.complex.com/music/2018/05/the-best-nicki-minaj-features/y-u-mad|title=The Best Nicki Minaj Features|access-date=August 21, 2020|magazine=Complex|date=May 8, 2018}}</ref> She "made almost a second career" out of her features, and is the female artist in hip hop who has done the most collaborations with lesser-known artists, ''[[Forbes]]'' stated. While "opting to spend the time and energy to help many acts secure their big break", Minaj also "cemented her status as a superstar—one who can succeed with any type of track."<ref name="Fs 24">{{cite web|url=https://www.forbes.com/sites/hughmcintyre/2023/06/13/nicki-minajs-collaborative-brilliance-the-business-plan-behind-all-of-her-features/|title=Nicki Minaj's Collaborative Brilliance: The Business Plan Behind All Of Her Features|work=Forbes|date=June 13, 2023|access-date=October 19, 2024|archive-url=https://web.archive.org/web/20230615225721/https://www.forbes.com/sites/hughmcintyre/2023/06/13/nicki-minajs-collaborative-brilliance-the-business-plan-behind-all-of-her-features/|archive-date=June 15, 2023|url-status=live}}</ref> Minaj became the most successful woman in hip hop history.<ref name=":053">{{cite news|last=Dazed|date=September 19, 2017|title=Nicki Minaj on ten years of revolutionising rap|language=en|work=Dazed|url=http://www.dazeddigital.com/music/article/37367/1/nicki-minaj-new-album-steven-klein-interview|access-date=October 29, 2018|archive-url=https://web.archive.org/web/20250120220028/http://www.dazeddigital.com/music/article/37367/1/nicki-minaj-new-album-steven-klein-interview|archive-date=January 20, 2025|url-status=live}}</ref><ref>{{cite web|title=Nicki Minaj Brings A Gone-Too-Soon Hip-Hop Star Back To The Top 10|url=https://www.forbes.com/sites/hughmcintyre/2024/11/26/nicki-minaj-brings-a-gone-too-soon-hip-hop-star-back-to-the-top-10/|work=[[Forbes]] |access-date=November 28, 2024|date=November 26, 2024|archive-url=https://web.archive.org/web/20241127180352/https://www.forbes.com/sites/hughmcintyre/2024/11/26/nicki-minaj-brings-a-gone-too-soon-hip-hop-star-back-to-the-top-10/|archive-date=November 27, 2024|url-status=live}}</ref> Michael Saponara of ''Billboard'' wrote that she "reaffirmed living legend status" by "hitting the road for the [[Pink Friday 2 World Tour]]"—which concluded as the highest-grossing tour by a female rapper and one of the top five highest-grossing hip hop tours in history—and proved herself as rap's "centrifugal female force".<ref name="bbhottestfemalerappers2024"/><ref name="BB HH">{{cite tweet|user=billboardhiphop|title=Nicki Minaj's #PinkFriday2 #GagCity World Tour Now Ranks Among The Top 5 Highest Grossing Hip Hop Tours in history|number=1845209561013109131|date=October 12, 2024|access-date=October 19, 2024|archive-url=https://ghostarchive.org/archive/7qysk|archive-date=October 19, 2024|url-status=live}}</ref><ref name="ranking"/> With the tour, Minaj "etched her name in the annals of music history", ''The Source'' noted, while she "leaves behind a legacy of groundbreaking achievements and artistry".<ref name="source may">{{cite web|url=https://thesource.com/2024/05/15/nicki-minaj-wraps-up-pink-friday-2-tour-with-record-breaking-success/|title=Nicki Minaj Wraps Up 'Pink Friday 2 Tour with Record-Breaking Success|date=May 15, 2024|access-date=October 11, 2024|work=[[The Source (magazine)|The Source]]}}</ref>
Minaj fought for [[music streaming|streaming]] to count toward artists' sales and certifications, and is credited with influencing this change in the industry.<ref name="RS stream">{{cite magazine|url=https://www.rollingstone.com/music/music-news/nicki-minaj-argues-streaming-should-count-toward-album-sales-63743/|title=Nicki Minaj Argues Streaming Should Count Toward Album Sales|magazine=Rolling Stone|date=December 17, 2015|access-date=September 17, 2024|archive-url=https://web.archive.org/web/20230713051158/https://www.rollingstone.com/music/music-news/nicki-minaj-argues-streaming-should-count-toward-album-sales-63743/|archive-date=July 13, 2023|url-status=live}}</ref><ref name="BI stream">{{cite web|url=https://www.businessinsider.com/riaa-streaming-certifications-2016-2|title=Nicki Minaj just won a major fight for streaming that will change the music industry|work=Business Insider|date=February 1, 2016|access-date=September 17, 2024|archive-url=https://web.archive.org/web/20190115225054/https://www.businessinsider.com/riaa-streaming-certifications-2016-2|archive-date=January 15, 2019|url-status=live}}</ref><ref name="chance">{{cite web|url=https://www.musictimes.com/articles/87539/20221021/chance-rapper-defends-nicki-minaj-resurfaced-video-watch.htm|title=Chance The Rapper Defends Nicki Minaj On Resurfaced Video [WATCH]|website=Music Times|date=October 21, 2022|access-date=September 17, 2024|archive-url=https://web.archive.org/web/20230325174923/https://www.musictimes.com/articles/87539/20221021/chance-rapper-defends-nicki-minaj-resurfaced-video-watch.htm|archive-date=March 25, 2023|url-status=live}}</ref> She has influenced or inspired many artists, including [[Tinashe]],<ref>{{cite web|last=Roth|first=Madeline|title=Tinashe on Nicki Minaj's Best Quality: 'She's Unapologetic'|url=http://www.mtv.com/news/2351719/tinashe-female-empowerment-lesson-nicki-minaj/|archive-url=https://web.archive.org/web/20151016152440/http://www.mtv.com/news/2351719/tinashe-female-empowerment-lesson-nicki-minaj/|url-status=dead|archive-date=October 16, 2015|access-date=August 10, 2020|publisher=MTV}}</ref> [[Cher Lloyd]],<ref>{{Cite web|title=Cher Lloyd Says Nicki Minaj Helped Change Pop Music: 'I Admire How Brave She Is'|url=https://www.capitalfm.com/artists/cher-lloyd/news/nicki-minaj-changed-pop/|access-date=August 13, 2020|publisher=[[Capital (radio network)|Capital]]|date=October 3, 2012}}</ref> [[Cupcakke]],<ref>{{Cite web|title=CupcakKe Interview – New Album, Hosting Hot Haus TV Show, Going to School With Chief Keef|date=October 29, 2021|url=https://www.youtube.com/watch?v=O7nDizgI00s&t=1768s|access-date=November 12, 2021|work=XXL|via=YouTube}}</ref> [[Billie Eilish]],<ref>{{Cite magazine|last=Kaufman|first=Gil|title=Here Are the Artists Who Inspired Billie Eilish Over the Years|url=https://www.billboard.com/music/pop/billie-eilish-musical-inspirations-9391977/|access-date=August 30, 2020|magazine=Billboard}}</ref> [[Lil Nas X]],<ref>{{Cite magazine|title=Lil Nas X on 'Montero (Call Me By Your Name)', forgiving himself, and being a Barb|url=https://ew.com/music/lil-nas-x-montero-pride-cover-story/|access-date=September 16, 2021|magazine=Entertainment Weekly|language=en}}</ref> [[Ms Banks]],<ref>{{Cite web|last=Mylrea|first=Hannah|date=November 14, 2017|title=Meet Ms Banks, Nicki Minaj's New Favourite Rapper|website=[[NME]]|url=https://www.nme.com/blogs/nme-radar/who-ms-banks-nicki-minajs-new-favourite-rapper-2158928|access-date=September 15, 2020}}</ref> [[Asian Doll]],<ref>{{Cite web|date=May 11, 2018|title=Asian Doll Talks Nicki Minaj Co-Sign: 'I Was the First One to Say She's My Idol'|url=https://thesource.com/2018/05/11/asian-doll-nicki-minaj/|work=[[The Source (magazine)|The Source]]|access-date=September 17, 2024}}</ref> [[Doja Cat]],<ref>{{Cite web|first=Stacy-Ann|last=Ellis|title=Doja Cat Is Here to Stay With Whimsical Wordplay and Bars That Bite|url=https://www.xxlmag.com/doja-cat-rapper-interview/|access-date=August 10, 2020|website=XXL|date=January 30, 2020 }}</ref> [[Megan Thee Stallion]],<ref>{{Cite web|date=March 11, 2020|title='She's An Inspiration' – Megan Thee Stallion Gush Over Nicki Minaj|url=https://www.informationng.com/2020/03/shes-an-inspiration-megan-thee-stallion-gush-over-nicki-minaj-video.html|access-date=April 8, 2021|website=Information Nigeria}}</ref> [[Mulatto (rapper)|Latto]],<ref>{{Cite web|title=Mulatto Says Nicki Minaj Inspired Her to Become a Rapper|url=https://www.rap-up.com/2021/01/26/nicki-minaj-inspired-mulatto-to-rap/|access-date=April 6, 2021|website=Rap-Up|archive-url=https://web.archive.org/web/20210308113521/https://www.rap-up.com/2021/01/26/nicki-minaj-inspired-mulatto-to-rap/|archive-date=March 8, 2021|url-status=dead}}</ref> [[Shenseea]],<ref>{{Cite web|title=Shenseea Was Inspired by Nicki Minaj and Rihanna|url=https://www.bet.com/video-clips/8cumf1/shenseea-was-inspired-by-nicki-minaj-and-rihanna|access-date=January 19, 2022|website=BET|date=August 5, 2021|language=en|archive-url=https://web.archive.org/web/20220211132420/https://www.bet.com/video-clips/8cumf1/shenseea-was-inspired-by-nicki-minaj-and-rihanna|archive-date=February 11, 2022|url-status=dead}}</ref> [[Bia (rapper)|BIA]],<ref>{{Cite web|title=Who is BIA? Meet the Boston rapper on the 'Whole Lotta Money' remix with Nicki Minaj|url=https://www.usatoday.com/story/entertainment/music/2021/07/09/who-bia-meet-rapper-whole-lotta-money-remix-nicki-minaj/7919592002/|website=USA Today|access-date=July 10, 2021}}</ref> [[Lakeyah]],<ref>{{Cite web|last=Torres|first=Bianca|title=Lakeyah Names Her Top Five Favorite Rappers|url=https://www.xxlmag.com/lakeyah-top-five-rappers/|access-date=January 9, 2022|website=XXL Mag|date=July 2, 2021 |language=en}}</ref> [[Luísa Sonza]],<ref>{{Cite magazine|last=Internet (amdb.com.br)|first=AMDB|title=Luísa Sonza lança 'Anaconda *o* ~~~', feat. com Mariah Angeliq: 'Fiz a cama; e ela deitou'|url=https://rollingstone.uol.com.br/musica/luisa-sonza-lanca-anaconda-o-feat-com-mariah-angeliq-fiz-cama-e-ela-deitou-entrevista/|access-date=December 9, 2021|magazine=Rolling Stone|date=December 8, 2021}}</ref> [[Coi Leray]],<ref>{{Cite web|title=Nicki Minaj probably one of the biggest female Trendsetters of all time. #inspired|url=https://twitter.com/coi_leray/status/1479535024550518786|access-date=January 7, 2022|via=Twitter|date=January 7, 2022|author=Coi Leray}}</ref> [[Maliibu Miitch]],<ref>{{Cite web|date=October 28, 2019|title=Bronx native Maliibu Miitch is going back to the roots of rap|url=https://www.wonderlandmagazine.com/2019/10/28/malibu-mitch-autumn-19-issue/|access-date=September 8, 2021|website=Wonderland|language=en-US}}</ref> [[City Girls]],<ref>{{Cite web|date=August 10, 2021|title=City Girls Still 'Hoping' To Work With Nicki Minaj Despite Yung Miami Instagram Block|last=Bustard|first=Andy|url=https://hiphopdx.com/news/id.63802/title.city-girls-still-hoping-to-work-with-nicki-minaj-despite-yung-miami-instagram-block|access-date=January 8, 2022|website=HipHopDX|archive-date=February 5, 2022|archive-url=https://web.archive.org/web/20220205005621/https://hiphopdx.com/news/id.63802/title.city-girls-still-hoping-to-work-with-nicki-minaj-despite-yung-miami-instagram-block|url-status=dead}}</ref> [[Baby Tate (rapper)|Baby Tate]],<ref>{{Cite web|last=III|first=Robby Seabrook|title=The Break Presents: Yung Baby Tate|url=https://www.xxlmag.com/yung-baby-tate-interview-the-break/|access-date=January 14, 2022|website=XXL Mag|date=February 6, 2019 |language=en}}</ref> [[Ice Spice]],<ref>{{Citation |title=Ice Spice on Becoming The Female Face of Bronx Drill | date=May 15, 2022 |url=https://www.youtube.com/watch?v=H91MADIRsQM |language=en |access-date=December 31, 2022}}</ref> [[Rico Nasty]],<ref>{{Cite web|last=Williams|first=Kyann-Sian|date=September 11, 2020|title=Rico Nasty: 'I definitely resonate with being a pop-punk princess'|url=https://www.nme.com/big-reads/rico-nasty-cover-interview-2020-iphone-nightmare-vacation-2750494|access-date=January 8, 2022|website=NME|language=en-GB}}</ref> [[Flo Milli]],<ref>{{Cite web|last=Iandoli|first=Kathy|title=Flo Milli Looks to Nicki Minaj for Career Inspiration|url=https://www.xxlmag.com/flo-milli-interview-nicki-minaj/|access-date=January 9, 2022|website=XXL Mag|date=January 18, 2021 |language=en}}</ref> [[Ivorian Doll]],<ref>{{Cite web|title='I'm Just Trying To Compete With The Boys': An Interview With Ivorian Doll|url=https://www.complex.com/music/2020/07/ivorian-doll-interview|access-date=January 9, 2022|website=Complex|date=July 31, 2020 |language=en}}</ref> [[Angel Haze]],<ref>{{Cite web|date=September 6, 2012|title=Angel Haze Speaks On Learned Technique, Inspiration From Drake & Nicki Minaj|url=https://hiphopdx.com/news/id.21042/title.angel-haze-speaks-on-learned-technique-inspiration-from-drake-nicki-minaj|access-date=January 11, 2022|website=HipHopDX|archive-date=January 11, 2022|archive-url=https://web.archive.org/web/20220111211826/https://hiphopdx.com/news/id.21042/title.angel-haze-speaks-on-learned-technique-inspiration-from-drake-nicki-minaj|url-status=dead}}</ref> [[Rubi Rose]],<ref>{{cite web|url=https://www.xxlmag.com/rubi-rose-interview-voice/|title=Rubi Rose's Distinctive Voice Fuels Her Rap Career Ahead of Debut Album|work=[[XXL (magazine)|XXL]]|date=May 20, 2021|access-date=September 17, 2024|archive-url=https://web.archive.org/web/20240419020415/https://www.xxlmag.com/rubi-rose-interview-voice/|archive-date=April 19, 2024|url-status=live}}</ref> [[Saweetie]],<ref>{{Cite magazine|last=Lyle|first=Ashley|date=February 14, 2018|title=Rising MC Saweetie Talks Viral Hit 'Icy Grl', Signing With Warner Bros. Records & the Value of Education|url=https://www.billboard.com/music/rb-hip-hop/saweetie-interview-icy-grl-warner-bros-deal-8099115/|access-date=September 10, 2019|magazine=Billboard}}</ref> and [[Doechii]].<ref>{{cite web|url=https://www.vulture.com/article/doechii-crazy-persuasive-profile.html|title=Doechii to the Front of the Class The rapper from Tampa is about to be everywhere|work=[[Vulture (website)|Vulture]]|date=July 18, 2022|access-date=October 19, 2024|archive-url=https://web.archive.org/web/20240913075946/https://www.vulture.com/article/doechii-crazy-persuasive-profile.html|archive-date=September 13, 2024|url-status=live}}</ref><ref>{{cite web|url=https://www.revolt.tv/article/2023-08-04/319817/doechii-talks-hip-hop-icons-and-pushing-the-boundaries-of-music-interview|title=Doechii pays homage to hip hop icons and talks pushing the boundaries of music genres|work=[[Revolt (TV network)|Revolt]]|date=October 4, 2023|access-date=October 19, 2024}}</ref>
Minaj's critically acclaimed verse on [[Kanye West]]'s "[[Monster (Kanye West song)|Monster]]"<ref>{{cite web|url=https://www.xxlmag.com/kanye-west-monster-lyrics-jay-z-rick-ross-nicki-minaj-bon-iver/|title=Kanye West's 'Monster' Lyrics Featuring Jay-Z, Rick Ross, Nicki Minaj and Bon Iver|date=March 20, 2021|access-date=September 3, 2024|work=[[XXL (magazine)|XXL]]}}</ref><ref>{{cite web|url=https://www.hotnewhiphop.com/324534-kanye-wests-monster-reaches-massive-spotify-milestone-news|title=Kanye West's "Monster" Reaches Massive Spotify Milestone|website=HNHH |date=September 26, 2022|access-date=September 3, 2024}}</ref><ref name="ES 23"/> helped establish her as a "top tier talent who dispelled gender distinctions in rap", and was "the first time the conversation was ever about a female out-classing her male contemporaries", ''[[Pitchfork (website)|Pitchfork]]'' stated.<ref name="PF 14">{{cite web|url=https://pitchfork.com/thepitch/612-the-nicki-minaj-singles-tournament-final/|title=The Nicki Minaj Singles Tournament: Final|first=Meaghan|last=Garvey|work=[[Pitchfork (website)|Pitchfork]]|date=December 26, 2014|access-date=September 3, 2024}}</ref><ref>{{cite web|url=https://www.bustle.com/articles/67663-kanye-wests-lecture-at-oxford-brings-up-gender-disparity-in-hip-hop-but-are-his-words-about|title=Was Kanye West Being Sexist Regarding Nicki Minaj?|work=[[Bustle (magazine)|Bustle]]|date=March 3, 2015|access-date=September 3, 2024}}</ref> The ''[[Los Angeles Times]]'' named her verse one of the 50 greatest moments in hip hop history, writing: "Minaj was already a multiplatinum headliner by the time she laid down her song-stealing verse on Kanye West's all-star romp "Monster" ... as much a defiant declaration of women's empowerment as a gloriously unhinged lyrical flex, [Minaj's performance] ranks as one of the most memorable mic drops in rap history."<ref>{{cite web|url=https://www.latimes.com/entertainment-arts/music/story/2023-08-07/hip-hop-50th-anniversary-greatest-moments|title=The 50 Greatest Moments in Hip-hop History|work=[[Los Angeles Times]]|date=August 7, 2023|access-date=September 3, 2024|archive-url=https://web.archive.org/web/20231218030343/https://www.latimes.com/entertainment-arts/music/story/2023-08-07/hip-hop-50th-anniversary-greatest-moments|archive-date=December 18, 2023|url-status=live}}</ref> West said that at one point he considered removing her verse from the track, because he was worried it would outshine his own work: "I knew people would say that was the best verse on the best Hip Hop album of all time or arguably top ten albums of all time. ... and people to this day would say to me 'My favorite thing was Nicki Minaj's verse'."<ref>{{Cite web|url=https://allhiphop.com/2013/11/26/kanye-west-talks-potentially-cutting-nicki-minajs-monster-verse-his-issues-with-nike-on-sway/|title=Kanye West Talks Potentially Cutting Nicki Minaj's "Monster" Verse & His Issues With Nike|last=Kyles|first=Yohance|website=[[AllHipHop]]|date=November 26, 2013|access-date=March 8, 2017}}</ref>
Minaj has been praised by other hip hop artists over the years. [[Rick Ross]] said he was impressed seeing Minaj write her verse on "Monster" and deliver her performance, deeming it the moment he knew she was "one of the greatest".<ref name="MTV ross 14">{{Cite news|url=http://www.mtv.com/news/1648837/rick-ross-says-monster-proves-nicki-minaj-is-one-of-the-greatest/|archive-url=https://web.archive.org/web/20140707034022/http://www.mtv.com/news/1648837/rick-ross-says-monster-proves-nicki-minaj-is-one-of-the-greatest/|url-status=dead|archive-date=July 7, 2014|title=Rick Ross Says 'Monster' Proves Nicki Minaj Is 'One Of The Greatest'|publisher=MTV News|access-date=March 8, 2017}}</ref> [[Cyhi the Prynce]] said he "couldn't believe she could rap that good as a young lady" and "knew she was special".<ref>{{cite web|url=https://hiphopdx.com/news/id.70814/title.nicki-minaj-shocked-cyhi-the-prynce-by-rewriting-recording-her-monster-verse|title=Nicki Minaj Shocked Cyhi The Prynce By Rewriting & Recording Her 'monster' Verse Live|work=[[HipHopDX]]|date=June 7, 2022|access-date=April 19, 2025|archive-url=https://web.archive.org/web/20240412210929/https://hiphopdx.com/news/id.70814/title.nicki-minaj-shocked-cyhi-the-prynce-by-rewriting-recording-her-monster-verse|archive-date=April 12, 2024|url-status=live}}</ref> Kanye West considered Minaj "the scariest artist in the game right now". "She has the most potential out of everyone to be the number-two rapper of all time, cause nobody's gonna be bigger than Eminem".<ref name="RS 10">{{cite magazine|url=https://www.rollingstone.com/music/music-news/nicki-minaj-the-new-queen-of-hip-hop-103304/|title=Nicki Minaj: The New Queen of Hip-Hop|date=December 9, 2010|access-date=September 17, 2024|magazine=Rolling Stone|archive-url=https://web.archive.org/web/20230407214739/https://www.rollingstone.com/music/music-news/nicki-minaj-the-new-queen-of-hip-hop-103304/|archive-date=April 7, 2023|url-status=live}}</ref> [[50 Cent]] called Minaj an "alpha female", adding that "[she] happens to be a girl, but that n***a is tough".<ref>{{cite web|url=https://www.rap-up.com/article/2020/07/04/50-cent-praises-nicki-minaj-eminem|title=50 Cent Calls Nicki Minaj 'Alpha Female,' Celebrates Eminem's Impact|work=[[Rap-Up]]|date=July 4, 2020|access-date=September 17, 2024|archive-url=https://web.archive.org/web/20240917114237/https://www.rap-up.com/article/2020/07/04/50-cent-praises-nicki-minaj-eminem|archive-date=September 17, 2024|url-status=dead}}</ref> [[RZA]] complimented her "versatility of lyricism and approach".<ref>{{cite web|url=https://www.distractify.com/p/when-is-kanye-releasing-new-album|title=RZA Is "Super Excited" for the Release of Kanye West's 10th Studio Album (EXCLUSIVE)|website=[[Distractify]]|date=July 19, 2021|access-date=September 17, 2024}}</ref> [[Ice-T]] said that Minaj "does her thing. She has her own way of doing it. She has an ill vocal delivery. She kind of reminds me of a female [[Busta Rhymes]], like how she throws her voice in different directions."<ref>{{cite magazine|url=https://www.rollingstone.com/music/news/q-a-ice-t-on-the-art-of-rap-why-nicki-minaj-is-real-hip-hop-20120611|title=QA: Ice-T on 'The Art of Rap,' Why Nicki Minaj Is 'Real Hip-Hop' |first=Sowmya|last=Krishnamurthy|date=June 11, 2012|magazine=Rolling Stone|access-date=August 3, 2012|archive-date=January 9, 2018|archive-url=https://web.archive.org/web/20180109064146/https://www.rollingstone.com/music/news/q-a-ice-t-on-the-art-of-rap-why-nicki-minaj-is-real-hip-hop-20120611|url-status=live}}</ref> [[Chance the Rapper]] highlighted Minaj's work "beyond hip hop ... streaming rights, like the rights of us like to let us connect the money were owed, the rights of ''Billboard'' to start accounting streams that they weren't counting - that was Nicki Minaj. So I respect Nicki in a whole different way."<ref name="chance"/>
== Achievements ==
{{see also|List of awards and nominations received by Nicki Minaj}}
Minaj is the recipient of numerous accolades, including nine [[American Music Award]]s, a [[Brit Awards|Brit Award]], eleven [[BET Awards]], five [[Billboard Music Award|''Billboard'' Music Award]]s, eight [[MTV Video Music Award]]s, twelve [[MTV Europe Music Awards]], six [[People's Choice Awards]], and a [[Soul Train Music Award]]. She has received a total of 12 [[Grammy Award]] nominations, including for [[Grammy Award for Best New Artist|Best New Artist]], [[Grammy Award for Best Rap Album|Best Rap Album]], and [[Grammy Award for Best Rap Performance|Best Rap Performance]]. She has won the [[MTV Video Music Award for Best Hip-Hop Video]] five times ("[[Super Bass]]", "[[Anaconda (Nicki Minaj song)|Anaconda]]", "[[Chun-Li (song)|Chun-Li]]", "[[Do We Have a Problem?]]" and "[[Super Freaky Girl]]") and won a [[Best Female Video|Best Female Video Award]] ("[[Starships (song)|Starships]]"). Minaj is the only rapper to win the [[BET Award for Best Female Hip Hop Artist]] eight times.<ref name="bethiphop2024"/> In 2012, ''[[Forbes]]'' included Minaj in its [[Forbes 30 Under 30|30 Under 30]] listicle of music.<ref>{{Cite magazine |title=Nicki Minaj |url=https://www.forbes.com/profile/nicki-minaj/ |access-date=October 21, 2024 |magazine=[[Forbes]]}}</ref> She was featured in the magazine's [[Forbes Celebrity 100|Celebrity 100]] listicle in 2015 and 2016.<ref name="celeb100_2015">{{Cite magazine |last=DeMers |first=Jayson |date=June 18, 2015 |title=Celebrity 100: 38. Nicki Minaj |url=https://www.forbes.com/pictures/fjim45ehli/38-nicki-minaj/ |access-date=November 18, 2024 |magazine=Forbes}}</ref><ref name="celeb100_2016">{{Cite magazine |last=Bridges |first=Frances |date=March 31, 2016 |title=Celebrity 100: Twitter's Most-Followed Superstars |url=https://www.forbes.com/pictures/emhi45ehmi/38-nicki-minaj/ |access-date=November 18, 2015 |magazine=Forbes}}</ref> In 2016, ''[[Time (magazine)|Time]]'' named Minaj one of the most influential people in the world in its annual [[Time 100|''Time'' 100]], and featured her on one of the physical covers of the issue.<ref>{{Cite magazine |date=April 21, 2016 |title=Nicki Minaj & Kendrick Lamar Named TIME's 100 Most Influential People |url=https://www.rap-up.com/2016/04/21/nicki-minaj-kendrick-lamar-named-time-100-most-influential-people/ |url-status=dead |archive-url=https://web.archive.org/web/20210930152026/https://www.rap-up.com/2016/04/21/nicki-minaj-kendrick-lamar-named-time-100-most-influential-people/ |archive-date=September 30, 2021 |access-date=June 9, 2020 |magazine=Rap-Up}}</ref><ref>{{Cite magazine |author=Lil Wayne |author-link=Lil Wayne |date=April 21, 2016 |title=Nicki Minaj |url=https://time.com/collection-post/4298230/nicki-minaj-2016-time-100/ |access-date=June 9, 2020 |magazine=Time |archive-date=January 18, 2022 |archive-url=https://web.archive.org/web/20220118083545/https://time.com/collection-post/4298230/nicki-minaj-2016-time-100/ |url-status=dead }}</ref> In 2019, [[Billboard Women in Music|''Billboard'' Women in Music]] awarded her with the Game Changer Award.<ref name="BB change">{{Cite magazine |last=Gracie |first=Bianca |date=December 12, 2019 |title=''Billboard'' Gamechanger Nicki Minaj on Why She'll Never 'Completely' Step Away From Music |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-game-changer-billboard-women-in-music-interview-8545934/ |access-date=August 26, 2021 |magazine=Billboard}}</ref> At the [[2023 MTV Europe Music Awards]], Minaj became the first black artist and the first female rapper to win the [[MTV Europe Music Award for Best US Act]].<ref>{{Cite magazine |last=Walcott |first=Escher |date=November 6, 2023 |title=Taylor Swift, Billie Eilish and Nicki Minaj Among 2023 MTV EMA Winners After Ceremony Cancellation |url=https://people.com/taylor-swift-billie-eilish-nicki-minaj-2023-mtv-ema-winners-8387655 |magazine=People |access-date=November 18, 2023 |archive-date=November 20, 2023 |archive-url=https://web.archive.org/web/20231120123644/https://people.com/taylor-swift-billie-eilish-nicki-minaj-2023-mtv-ema-winners-8387655 |url-status=live}}</ref> In 2025, ''Billboard'' ranked her at number 16 on its "Top 100 Women Artists of the 21st Century" list.<ref>{{cite magazine |first1=Trevor |last1=Anderson |first2=Jim |last2=Asker |first3=Pamela |last3=Bustios |first4=Keith |last4=Caulfield |first5=Eric |last5=Frankenberg |first6=Kevin |last6=Rutherford |first7=Gary |last7=Trust |first8=Xander |last8=Zellner |title=Billboard's Top 100 Women Artists of the 21st Century Chart, Nos. 100-1 |magazine=[[Billboard (magazine)|Billboard]] |url= https://www.billboard.com/lists/top-women-artists-21st-century-chart/ |date=March 19, 2025 |accessdate=March 19, 2025 |archive-url=https://web.archive.org/web/20250319183615/https://www.billboard.com/lists/top-women-artists-21st-century-chart/ |url-status=live |archive-date=March 19, 2025}}</ref>
As of 2024, Minaj has sold over 100 million records worldwide, making her the [[List of best-selling female rappers|best-selling female rapper]] and one of the [[List of best-selling music artists|best-selling music artists]].<ref name="post">{{cite news |last=Harris |first=Vincent |date=September 30, 2024 |title=Columbia Concerts: 9 shows to see this October, including Nicki Minaj, Method Man and Redman |url=https://www.postandcourier.com/free-times/music/october-concerts-live-music-shows-columbia-sc/article_bac35a4a-7c1a-11ef-8085-3723be13c3ac.html |url-status=live |archive-url=https://web.archive.org/web/20241204004542/https://www.postandcourier.com/free-times/music/october-concerts-live-music-shows-columbia-sc/article_bac35a4a-7c1a-11ef-8085-3723be13c3ac.html |archive-date=December 4, 2024 |access-date=October 11, 2024 |newspaper=[[The Post and Courier]] }}</ref> She is also the [[List of highest-grossing live music artists#Rankings by genre|highest-grossing]] [[List of highest-grossing live music artists#Rankings by genre|female hip hop touring act]]. Minaj achieved the largest album sales week of the 21st century among female rappers with her debut studio album ''Pink Friday'', and second-largest of all time after [[Lauryn Hill]]'s ''[[The Miseducation of Lauryn Hill]] ''(1998).<ref name="PF21stcentury">{{cite magazine |last=Caulfield |first=Keith |date=December 2010 |title=Kanye West, Nicki Minaj Score Big Debuts on Billboard 200 |url=https://www.billboard.com/articles/news/950035/kanye-west-nicki-minaj-score-big-debuts-on-billboard-200 |access-date=October 28, 2024 |magazine=[[Billboard (magazine)|Billboard]]}}</ref> In 2010, she became the first female solo artist to have seven songs on the ''Billboard'' Hot 100 simultaneously.<ref>{{cite web|url=http://rapfix.mtv.com/2010/10/08/nicki-minaj-makes-history-with-seven-billboard-hot-100-songs/|title=Nicki Minaj Makes History With Seven Billboard Hot 100 Songs|publisher=MTV RapFix. Viacom|date=October 8, 2010|access-date=August 13, 2014|url-status=dead|archive-url=https://web.archive.org/web/20101011085217/http://rapfix.mtv.com/2010/10/08/nicki-minaj-makes-history-with-seven-billboard-hot-100-songs/|archive-date=October 11, 2010}}</ref> In 2017, Minaj broke the record for the most Hot 100 entries by a female artist (until December 2020), surpassing [[Aretha Franklin]], and in 2018, she became the first woman to accumulate 100 entries on the Hot 100.<ref name="ReferenceD">{{Cite magazine|title=Nicki Minaj Makes History as First Woman With 100 Appearances on Billboard Hot 100|url=https://www.billboard.com/pro/nicki-minaj-first-woman-100-hits-hot-100/|access-date=March 21, 2021|magazine=Billboard|language=en}}</ref><ref name="ReferenceE">{{Cite magazine|title=Nicki Minaj Passes Aretha Franklin for Most Billboard Hot 100 Hits of Any Female Artist|url=https://www.billboard.com/pro/nicki-minaj-passes-aretha-franklin-for-most-billboard-hot-100/|access-date=March 21, 2021|magazine=Billboard|language=en}}</ref><ref>{{Cite web|date=November 6, 2018|title=Nicki Minaj Becomes First Woman Ever With 100 Billboard Hot 100 Entries|url=https://www.okayplayer.com/music/nicki-minaj-100-billboard-entries.html|access-date=March 21, 2021|website=Okayplayer|language=en-US|archive-url=https://web.archive.org/web/20220107212544/https://www.okayplayer.com/music/nicki-minaj-100-billboard-entries.html|archive-date=January 7, 2022|url-status=dead}}</ref><ref>{{Cite magazine|title=Doja Cat's 'Say So,' Featuring Nicki Minaj, Tops Billboard Hot 100, Becoming the First No. 1 For Each|url=https://www.billboard.com/articles/business/chart-beat/9375563/doja-cat-nicki-minaj-say-so-number-one|access-date=March 21, 2021|magazine=Billboard|language=en}}</ref> Her 2019 collaboration with [[Karol G]], "[[Tusa (song)|Tusa]]", was the first song by two lead female artists to debut atop the [[Hot Latin Songs|''Billboard'' Hot Latin Songs]] chart.<ref>{{cite magazine |last=Bustios |first=Pamela |date=November 20, 2019 |title=Karol G & Nicki Minaj's 'Tusa' Becomes First Song by Two Lead Female Artists to Debut at No. 1 on Hot Latin Songs |url=https://www.billboard.com/articles/business/chart-beat/8544328/karol-g-nicki-minaj-debut-number-one-latin-songs |url-access=limited |access-date=October 28, 2024 |magazine=[[Billboard (magazine)|Billboard]]}}</ref> In 2022, with the number-one Hot 100 debut of "Super Freaky Girl", she became the first solo female rapper to debut atop the chart since Hill in 1998.<ref name="SFG_hot100"/> Minaj has the most number-ones, top-tens, and appearances on [[Hot R&B/Hip-Hop Songs]] (8, 47, and 145) among female artists.<ref>{{cite magazine |title=Nicki Minaj (Chart History): Hot Rap Songs |url=https://www.billboard.com/artist/Nicki-Minaj/chart-history/rap/ |access-date=October 28, 2024 |magazine=Billboard}}</ref><ref>{{cite magazine |date= |title=Nicki Minaj (Chart History): Hot R&B/Hip-Hop Songs |url=https://www.billboard.com/artist/nicki-minaj/chart-history/bsi/ |access-date=October 28, 2024 |magazine=Billboard}}</ref> She has 23 top-ten singles on the ''Billboard'' Hot 100, the most for any female rapper.<ref>{{Cite news |last=Debbarma |first=Tanushree |date=July 4, 2023 |title=Nicki Minaj becomes 3rd rapper with most Top 10 hits in ''Billboard'' Hot 100 history |url=https://www.dailyo.in/entertainment/nicki-minaj-becomes-3rd-rapper-with-most-top-10-hits-in-billboard-hot-100-history-40408/ |access-date=October 28, 2024 |work=DailyO |publisher=[[India Today]]}}</ref> She is the female rapper with the most Hot 100 entries, top 40 entries, and [[Digital Songs]] chart-toppers.<ref>{{Cite magazine |last=McIntyre |first=Hugh |date=January 18, 2024 |title=Nicki Minaj Is Beating Every Other Artist With Her New No. 1 Hit–Except Taylor Swift |url=https://www.forbes.com/sites/hughmcintyre/2024/01/18/nicki-minaj-is-beating-every-other-artist-with-her-new-no-1-hitexcept-taylor-swift/ |access-date=October 28, 2024 |magazine=Forbes}}</ref> In 2023, she became the first female rapper to earn three number-one albums on the ''Billboard'' 200.<ref name="pf2_charttop" /> Her fifth studio album ''Pink Friday 2'' earned the highest first-week vinyl sales for a female rap album in US history.<ref name="pf2_charttop" /> Her [[Pink Friday 2 World Tour]] (2024) grossed over US$108 million, becoming the fourth-highest-grossing tour by a rapper or hip hop artist in history, the highest-grossing tour by a female rapper, and the first among female rappers to surpass US$100 million in revenue. It also made Minaj the fifth Black female artist in history to surpass the mark from a single tour.<ref name="BB tour">{{Cite magazine |last=Frankenberg |first=Erik |date=May 16, 2024 |title=Nicki Minaj's Pink Friday 2 World Tour Is Already the Highest Grossing Rap Tour for a Woman — and It Isn't Over Yet |url=https://www.billboard.com/music/chart-beat/nicki-minaj-pink-friday-2-world-tour-highest-grossing-rap-tour-woman-1235684476/ |access-date=May 23, 2024 |magazine=Billboard|archive-url=https://web.archive.org/web/20250531154136/https://www.billboard.com/music/chart-beat/nicki-minaj-pink-friday-2-world-tour-highest-grossing-rap-tour-woman-1235684476/|archive-date=May 31, 2025|url-status=live}}</ref><ref name="BB HH"/><ref name="ranking">{{Cite web|date=October 16, 2024|title=50 Cent Disputes Nicki Minaj's Tour Ranking|url=https://www.hot97.com/news/50-cent-disputes-nicki-minajs-tour-ranking/|access-date=October 19, 2024|publisher=[[Hot 97]]|archive-url=https://web.archive.org/web/20241021093612/https://www.hot97.com/news/50-cent-disputes-nicki-minajs-tour-ranking/|archive-date=October 21, 2024|url-status=bot: unknown|last=Aydelotte|first=Bato}}</ref> In December 2025, RapTV ranked it as the sixth-highest-grossing tour by a rap or hip–hop artist.<ref name="RapTV tour"/> In January 2016, Minaj had her 26th number 1 on the ''Billboard'' [[Rap Digital Song Sales]] chart with "My Life" from ''Pink Friday 2'', breaking Drake's record.<ref>{{cite tweet|user=billboardcharts|title=@NICKIMINAJ earns a record-breaking 26th No. 1 on the Rap Digital Song Sales chart with "My Life."|number=2011176714567848071|date=January 13, 2026|access-date=January 25, 2026|archive-url=https://ghostarchive.org/archive/NNwgm|archive-date=January 25, 2026|url-status=live}}</ref>
Minaj is the first woman to have appeared on the ''[[Forbes]]'' "Hip Hop Cash Kings" list since its inception in 2007, having made four consecutive appearances between 2011 and 2014.<ref>{{cite news|last=O'Malley Greenburg|first=Zack|title=Cash Kings 2011: Hip-Hop's Top Earners|work=Forbes|url=https://www.forbes.com/sites/zackomalleygreenburg/2011/08/09/cash-kings-2011-hip-hops-top-earners/}}</ref><ref>{{cite news|last=Greenburg|first=Zack|title=Cash Kings 2012: Hip-Hop's Top Earners|work=Forbes|url=https://www.forbes.com/pictures/eeel45jjgd/8-nicki-minaj-15-5-million-2/#gallerycontent|access-date=September 5, 2012}}</ref><ref>{{cite news|last=O'Malley Greenburg|first=Zack|title=Cash Kings 2013: The World's Highest-Paid Hip-Hop Artists|work=Forbes|url=https://www.forbes.com/sites/zackomalleygreenburg/2013/09/24/cash-kings-2013-the-worlds-highest-paid-hip-hop-artists/|access-date=November 20, 2013}}</ref><ref>{{cite web|last=O'Malley Greenburg|first=Zack|title=Cash Kings 2014: The World's Highest-Paid Hip-Hop Acts|url=https://www.forbes.com/sites/zackomalleygreenburg/2014/09/23/cash-kings-2014-the-worlds-highest-paid-hip-hop-acts/|access-date=October 2, 2014|website=Forbes}}</ref> In 2010, she became the first woman to appear on [[MTV's Hottest MCs in the Game|MTV's Annual Hottest MC List]] since its inception in 2007.<ref>{{cite web|url=http://www.mtv.com/news/1650583/nicki-minaj-makes-hottest-mcs-in-the-game-debut-at-6/|archive-url=https://web.archive.org/web/20140814034643/http://www.mtv.com/news/1650583/nicki-minaj-makes-hottest-mcs-in-the-game-debut-at-6/|url-status=dead|archive-date=August 14, 2014|title=Nicki Minaj Makes Hottest MCs In The Game Debut At #6!|last=Cantor|first=Paul|publisher=MTV News. Viacom|date=October 21, 2010|access-date=August 13, 2014}}</ref> The following year, she was ranked sixth on the ''[[Rolling Stone]]'' master ranking of the "Kings of Hip Hop", which is based on record sales and social media metrics.<ref>{{Cite web|date=August 15, 2011|title=Rolling Stone Master Ranking King Of Hip Hop|url=http://www.idolator.com/5976601/eminem-king-of-hip-hop-rolling-stone/rolling-stone-master-ranking-king-of-hip-hop|website=idolator}}{{dead link|date=August 2025|bot=medic}}{{cbignore|bot=medic}}</ref> In 2019, she became the first female rapper to achieve 100 million certified units by the [[Recording Industry Association of America]] (RIAA), across her albums and songs.<ref>{{Cite web |date=February 2, 2019 |title=Nicki Minaj becomes first female rapper to top 100 million RIAA certified units |url=https://www.revolt.tv/article/2019-02-07/45532/nicki-minaj-becomes-first-female-rapper-to-top-100-million-riaa-certified-units/ |access-date=October 8, 2024 |website=[[Revolt TV]]}}</ref> In the U.S, she has 11 million certified album units and 54.5 million certified digital singles as a lead artist.<ref>{{cite web |title=Gold & Platinum – Top Artists (Digital Singles) |url=https://www.riaa.com/gold-platinum/?tab_active=top_tallies&ttt=TAS |access-date=December 29, 2025 |publisher=[[Recording Industry Association of America]]}}</ref> All of Minaj's studio albums have been certified platinum or higher by the RIAA, making her the female rapper with the second-most platinum albums in the US (five), behind [[Missy Elliott]], with six.<ref>{{cite web|title=Missy Elliott Becomes First Female Rapper With Six Straight Platinum Albums: 'So Grateful'|date=December 28, 2025 |url=https://www.complex.com/music/a/treyalston/missy-elliott-six-straight-platinum-albums|publisher=Complex}}</ref> Her highest-certified album by the RIAA is ''[[Pink Friday: Roman Reloaded]]'', at quadruple platinum.<ref>{{cite web|title=Gold & Platinum | American album certifications – Nicki Minaj – Pink Friday: Roman Reloaded|url=https://www.riaa.com/gold-platinum/?tab_active=default-award&ar=Nicki+Minaj&ti=Pink+Friday%3A+Roman+Reloaded&format=Album&type=#search_section|publisher=Recording Industry Association of America|access-date=November 19, 2024}}</ref> Three of her songs have been certified [[RIAA certification|diamond]] or higher by the RIAA: "Super Bass" (twelve-times platinum), "Bang Bang" (with [[Jessie J]] and [[Ariana Grande]]), and "Starships".<ref>{{Cite web |last=Oatman |first=Alexis |date=March 31, 2024 |title=Nicki Minaj's 'Super Bass' Earns Highest Certified Single Ever, Dethrones Cardi B's 'Bodak Yellow' |url=https://www.hot97.com/news/nicki-minaj-super-bass-cardi-b/ |access-date=October 9, 2024 |publisher=[[WQHT]]}}</ref> "Starships" made Minaj the first female rapper with multiple solo diamond-certified songs (two), and tied her with Cardi B for the most RIAA diamond-certified tracks among female rappers (three).<ref>{{Cite magazine |last=Roundtree |first=Caesar |date=November 18, 2024 |title=Nicki Minaj Sets New Historic Rap Achievement |url=https://glittermagazine.co/2024/11/18/nicki-minaj-sets-new-historic-rap-achievement/ |access-date=November 19, 2024 |magazine=Glitter}}</ref><ref name="starshipdiamond"/>
In 2021, the music video for "Anaconda" became the first female rap solo song to surpass one billion views on YouTube.<ref>{{Citation|title=Nicki Minaj – Anaconda| date=August 19, 2014 |url=https://www.youtube.com/watch?v=LDZX4ooRsWs|language=en|access-date=April 3, 2021}}</ref><ref name=":12">{{Cite web|title=Nicki Minaj Shatters Another Record After "Anaconda" Video Hits 1 Billion Views|url=https://www.hotnewhiphop.com/nicki-minaj-shatters-another-record-after-anaconda-video-hits-1-billion-views-news.129633.html|access-date=April 3, 2021|website=[[HotNewHipHop]]|date=April 3, 2021|archive-url=https://web.archive.org/web/20210403210353/https://www.hotnewhiphop.com/nicki-minaj-shatters-another-record-after-anaconda-video-hits-1-billion-views-news.129633.html|archive-date=April 3, 2021|url-status=dead}}</ref> In total, Minaj has nine music videos with over one billion views across all credits on YouTube, and is the first female rapper to have two solo music videos with over 1 billion views on the platform.<ref name="BE_bn">{{Cite web |last=Abdur-Rahman |first=Nahlah |date=October 27, 2023 |title=Nicki Minaj Bussed Down A Billion Views On YouTube—Twice |url=https://www.blackenterprise.com/nicki-minaj-billion-views-youtube/ |access-date=October 9, 2024 |website=[[Black Enterprise]]}}</ref> Two of those nine music videos have surpassed two billion views: "[[Bang Bang (Jessie J, Ariana Grande and Nicki Minaj song)|Bang Bang]]" (with [[Jessie J]] and [[Ariana Grande]]) and "[[Side to Side]]" (with Grande).<ref name="bangbang2bn">{{Cite web |date=July 3, 2024 |title="Bang Bang" into the record books: Jessie/Ari/Nicki collab hits new YouTube milestone |url=https://www.ks95.com/bang-bang-into-the-record-books-jessie-ari-nicki-collab-hits-new-youtube-milestone/ |access-date=October 9, 2024 |publisher=[[KSTP-FM]]}}</ref> Minaj was the fifth-most-streamed woman and most-streamed female rapper of the 2010s decade on Apple Music.<ref>{{cite web|last=Burch|first=Sean|title=Ariana Grande Tops List of Most Streamed Female Artists on Apple Music (Exclusive)|url=https://www.thewrap.com/apple-music-most-streamed-females-ariana-grande-taylor-swift/|access-date=October 19, 2024|work=[[TheWrap]]|date=March 8, 2019 }}</ref> Minaj is the most-followed female rapper on Spotify, with over 34.1 million followers.<ref>{{Cite web |date=August 24, 2023 |title=Most Followed Artists on Spotify |url=https://volt.fm/most-followed-artists/ |access-date=November 18, 2024 |website=Volt.FM}}</ref> In 2022, Minaj became the first rapper to reach 200 million followers on Instagram, and in 2024 was the [[List of most-followed Instagram accounts|most-followed rapper]] with over 227 million followers.<ref>{{Cite magazine |date=July 26, 2022 |title=Nicki Minaj Becomes First Rapper To Reach 200 Million Instagram Followers |url=https://www.maxim.com/entertainment/nicki-minaj-becomes-first-rapper-to-reach-200-million-instagram-followers/ |access-date=August 18, 2022 |magazine=[[Maxim (magazine)|Maxim]]|archive-url=https://web.archive.org/web/20250826111114/https://www.maxim.com/entertainment/nicki-minaj-becomes-first-rapper-to-reach-200-million-instagram-followers/|archive-date=August 26, 2025|url-status=live}}</ref><ref>{{Cite magazine |last=Gupta |first=Manas Sen |date=January 21, 2024 |title=These Are The Most-Followed Instagram Accounts In The World |url=https://www.augustman.com/my/entertainment/culture/events/these-are-the-most-followed-instagram-accounts-in-the-world/ |access-date=February 20, 2024 |magazine=[[August Man]]}}</ref> She deactivated her Instagram account in October 2025.<ref>{{cite magazine|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-deactivates-instagram-1236093877/|title=Nicki Minaj Deactivates Her Instagram Account Without Explanation|magazine=Billboard|date=October 21, 2025|access-date=January 30, 2026|archive-url=https://web.archive.org/web/20251024034413/https://www.billboard.com/music/rb-hip-hop/nicki-minaj-deactivates-instagram-1236093877/|archive-date=October 24, 2025|url-status=live}}</ref>
== Other activities ==
In March 2023, Minaj announced in an episode of her radio show, ''Queen Radio'', that she was starting a record label imprint called Heavy On It Records, under Republic Records.<ref>{{cite magazine|first=Mitchell|last=Peters|url=https://www.billboard.com/music/music-news/nicki-minaj-announces-record-label-launch-queen-radio-1235279961/|title=Nicki Minaj Announces Launch of Her Own Record Label on 'Queen Radio'|magazine=[[Billboard (magazine)|Billboard]]|date=March 4, 2023|access-date=April 16, 2023}}</ref><ref>{{cite web|first=Starr|last=Savoy|url=https://www.complex.com/music/nicki-minaj-record-label-launch|title=Nicki Minaj Announces Launch of Record Label, Reveals Roster|work=[[Complex (magazine)|Complex]]|date=March 5, 2023|access-date=April 16, 2023}}</ref><ref>{{cite web|first=Tyler Darama|last=Kelly|url=https://www.thelineofbestfit.com/news/nicki-minaj-officially-announces-record-label-and-shares-ice-spice-remix-princess-diana|title=Nicki Minaj officially announces record label and shares Ice Spice remix 'Princess Diana'|website=[[The Line of Best Fit]]|date=April 14, 2023|access-date=April 16, 2023}}</ref>
=== Endorsements ===
In November 2010, Minaj collaborated with [[MAC Cosmetics]] and launched the lipstick "Pink 4 Friday", sold to promote her debut studio album ''Pink Friday''.<ref>{{Cite web |date=November 24, 2010 |title=Nicki Minaj and M.A.C Launch Pink Friday Lipstick |url=https://people.com/style/nicki-minaj-and-m-a-c-launch-pink-friday-lipstick/ |access-date=January 15, 2023 |website=People |publisher=[[Dotdash Meredith]] |language=en}}</ref> In 2011, [[Mattel]] crafted a [[Barbie]] doll inspired by the rapper's likeness for charity, which she described as a "major moment" in her career.<ref name="Barbie">{{Cite magazine |last=Schneider |first=Marc |date=December 2, 2011 |title=Nicki Minaj: Barbie Doll a 'Very Major Moment' for Me |url=https://www.billboard.com/articles/news/464862/nicki-minaj-barbie-doll-a-very-major-moment-for-me |magazine=Billboard |access-date=September 16, 2020}}</ref> It was auctioned on the [[Charitybuzz]] website, with proceeds benefiting nonprofit [[Project Angel Food]].<ref name=":17">{{Cite magazine |last=Hampp |first=Andrew |date=December 2, 2011 |title=Exclusive: See One-of-a-Kind Nicki Minaj Barbie Doll (Photo) |url=https://www.billboard.com/music/music-news/exclusive-see-one-of-a-kind-nicki-minaj-barbie-doll-photo-1158761/ |access-date=January 15, 2023 |magazine=Billboard |publisher=Penske Media Corporation |language=en-US}}</ref>
Between 2012 and 2013, Minaj launched her own range of lip products for [[MAC Cosmetics]]' Viva Glam.<ref>{{Cite news |last=Ramos |first=Dorkys |date=February 4, 2012 |title=Pucker Up: Nicki Minaj's and Ricky Martin's Viva Glam Lipstick |url=https://www.bet.com/article/w2naiq/pucker-up-nicki-minaj-and-ricky-martin-s-viva-glam-lipstick |access-date=August 18, 2022 |website=[[BET]] |language=en |archive-date=August 18, 2022 |archive-url=https://web.archive.org/web/20220818160212/https://www.bet.com/article/w2naiq/pucker-up-nicki-minaj-and-ricky-martin-s-viva-glam-lipstick |url-status=dead}}</ref><ref>{{Cite web |last=Chan |first=Jennifer |date=December 5, 2012 |title=Nicki Minaj Unveils New MAC Cosmetics Ad for Viva Glam Campaign |url=https://www.eonline.com/news/368887/nicki-minaj-unveils-new-mac-cosmetics-ad-for-viva-glam-campaign |access-date=August 18, 2022 |publisher=[[E!#E! Online|E!]]}}</ref><ref>{{Cite web |last=Burton |first=Cinya |date=April 26, 2013 |title=Nicki Minaj's MAC Lipstick Is Viva Glam's Best-Selling Ever |url=https://www.eonline.com/ca/news/411822/nicki-minaj-s-tweets-mac-lipstick-is-viva-glam-s-best-selling-ever |access-date=August 24, 2022 |publisher=[[E!#E! Online|E!]]}}</ref> In 2012, she appeared in advertisements for [[Pepsi]]'s "LiveForNow" campaign, and signed an endorsement deal with [[Adidas]] for the brand's fall and winter campaigns later that year.<ref>{{cite web|last=Sanchez|first=Karizza|title=Nicki Minaj Signs Endorsement Deal With adidas|url =http://www.complex.com/style/2012/04/nicki-minaj-signs-endorsement-deal-with-adidas|website=Complex Media|date=April 14, 2012 |access-date=November 20, 2013}}</ref>
In April 2013, Minaj partnered with [[Beats Electronics]] to introduce her "Pink Pill" speakers.<ref>{{cite web|title=Beats By Dr. Dre Portable Pill Speaker Now Pretty In Pink, Compliments of Superstar Nicki Minaj, Exclusively From AT&T|url=http://www.beatsbydre.com/on/demandware.store/Sites-beats-Site/default/NewsAndPressRelease-ShowArticleContent?articleID=2013_PR_PinkPillNickiMinaj_April4&articleType=NewsAndEvents&articleCategory=All|publisher=Beats Electronics LLC|access-date=November 20, 2013}}</ref><ref>{{cite web|last=Gleckman|first =Alexander|title=Watch Nicki Minaj Behind the Scenes at Her 'Pink Pill Commercial'|date =April 5, 2013|url =http://www.complex.com/music/2013/04/watch-nicki-minaj-behind-the-scenes-at-her-pink-pill-commercial|publisher=Complex Media|access-date=November 20, 2013}}</ref> From 2013 to 2015, she released seasonal clothing collections for Kmart.<ref>{{Cite magazine |date=October 16, 2013 |title=Nicki Minaj Unveils Kmart Clothing Line |url=https://www.billboard.com/music/music-news/nicki-minaj-unveils-kmart-clothing-line-5755743/ |access-date=August 22, 2022 |magazine=[[Billboard (magazine)|Billboard]]}}</ref><ref>*{{Cite web |last=Keirans |first=Maeve |date=February 25, 2014 |title=Nicki Minaj's Kmart Spring Collection Is Full Of Chain Prints |url=https://www.mtv.com/news/uw24ri/nicki-minaj-kmart-spring-collection-chain-print |archive-url=https://web.archive.org/web/20220822225729/https://www.mtv.com/news/uw24ri/nicki-minaj-kmart-spring-collection-chain-print |url-status=dead |archive-date=August 22, 2022 |access-date=August 22, 2022 |publisher=[[MTV]]}}
*{{Cite magazine |last=Nostro |first=Lauren |date=January 30, 2014 |title=EXCLUSIVE: Here's a Sneak Peek of Nicki Minaj's Spring Collection For Kmart Paired With Lyrics For Every Outfit |url=https://www.vice.com/en/article/nicki-minaj-clothing-line-exclusives/ |access-date=August 22, 2022 |magazine=[[Vice (magazine)|Vice]]}}
*{{Cite web |title=Nicki Minaj's Summer 2014 Collection for Kmart: 10 Looks We Love |url=https://www.bet.com/photo-gallery/8d7lzl/nicki-minaj-s-summer-2014-collection-for-kmart-10-looks-we-love/h00ei0 |access-date=August 22, 2022 |publisher=[[BET]] |archive-url=https://web.archive.org/web/20220825232827/https://www.bet.com/photo-gallery/8d7lzl/nicki-minaj-s-summer-2014-collection-for-kmart-10-looks-we-love/h00ei0 |archive-date=August 25, 2022 |url-status=dead}}
*{{Cite AV media |url=https://m.youtube.com/watch?v=SEGzwmVdKNU |title=The Nicki Minaj Holiday '14 Collection at Kmart |date=October 23, 2014 |publisher=Nicki Minaj |access-date=August 22, 2022 |via=YouTube}}</ref><ref>* {{Cite magazine |date=November 28, 2015 |title=Nicki Minaj Models Kmart Couture Collection |url=https://hiphopdx.com/news/id.36441/title.nicki-minaj-models-kmart-couture-collection |access-date=August 22, 2022 |magazine=[[HipHopDX]] |archive-url=https://web.archive.org/web/20220822224217/https://hiphopdx.com/news/id.36441/title.nicki-minaj-models-kmart-couture-collection |archive-date=August 22, 2022 |url-status=dead}}
* {{Cite press release |title=Nicki Minaj Turns Up Cyber Monday With Exclusive Holiday Capsule Collection At Kmart |date=November 30, 2015 |url=https://www.prnewswire.com/news-releases/nicki-minaj-turns-up-cyber-monday-with-exclusive-holiday-capsule-collection-at-kmart-300185590.html |access-date=August 22, 2022 |type=Press release |agency=[[PR Newswire]]}}</ref> In March 2015, Minaj was announced as a co-owner of the [[music streaming]] service [[Tidal (service)|Tidal]] alongside [[Jay-Z]] and other artists. In December 2016, Minaj and [[Glu Mobile]] released ''Nicki Minaj: The Empire'', a [[Role-playing video game|role-playing]] mobile game for [[iOS]] and [[Android (operating system)|Android]].<ref>{{Cite magazine |last=Bruner |first=Raisa |date=December 7, 2016 |title=Nicki Minaj Will Help You Build Your Own Virtual Rap Empire With Her New Mobile Game |url=https://time.com/4594262/nicki-minaj-empire-game/ |access-date=October 9, 2024 |magazine=Time}}</ref>
In 2017, Minaj released two lipsticks in collaboration with [[MAC Cosmetics]]<ref>*{{Cite magazine |last=Hubbard |first=Lauren |date=August 17, 2022 |title=Nicki Minaj and MAC Are Teaming Up for a New Collection |url=https://www.harpersbazaar.com/beauty/makeup/a12047922/nicki-minaj-and-mac-are-teaming-up-for-a-new-collection/ |access-date=August 24, 2022 |magazine=[[Harper's Bazaar]]}}
*{{Cite magazine |last=Saunders |first=Nicole |date=August 22, 2017 |title=Nicki Minaj and MAC Cosmetics Are Together Again With #NickiNudes |url=https://www.billboard.com/culture/lifestyle/nicki-minaj-mac-cosmetics-collaboration-7940845/ |access-date=August 24, 2022 |magazine=[[Billboard (magazine)|Billboard]]}}</ref> and starred in [[H&M]]'s holiday campaign.<ref>{{Cite magazine |last=Alexis-Fisher |first=Lauren |date=November 9, 2017 |title=Nicki Minaj Transforms From Mom-Next-Door Into Magical Fairy in H&M Holiday Video |magazine=[[Harper's Bazaar]] |url=https://www.harpersbazaar.com/fashion/trends/a13487177/nicki-minaj-hm-holiday-campaign/ |access-date=August 16, 2020}}</ref> The following year, she appeared in campaigns for [[Mercedes-Benz]],<ref>{{Cite web |last=Williams |first=Aaron |date=March 29, 2018 |title=Nicki Minaj Is The Best Driving Buddy Around In A New Mercedes Benz Commercial |url=https://uproxx.com/music/nicki-minaj-mercedes-benz-commercial/ |access-date=April 3, 2023 |website=[[Uproxx]] |publisher=[[Warner Music Group]]}}</ref> [[EA Sports]]' [[Madden NFL 19]],<ref>*{{Cite magazine |last=Fogel |first=Stefanie |date=August 2, 2018 |title=New 'Madden 19' Trailer Features Nicki Minaj, Lil Dicky And More |url=https://variety.com/2018/gaming/news/madden-19-trailer-nicki-minaj-lil-dicky-1202893363/ |access-date=August 24, 2022 |magazine=[[Variety (magazine)|Variety]]}}
*{{Cite magazine |last=Traina |first=Jimmy |date=August 2, 2018 |title=New 'Madden NFL 19' trailer features slew of stars |url=https://www.si.com/extra-mustard/2018/08/02/traina-thoughts-new-madden-commercial-goes-heavy-big-names-comedy |access-date=August 24, 2022 |magazine=[[Sports Illustrated]]}}</ref><ref>{{Cite web |last=Ruppert |first=Liana |date=November 15, 2018 |title='Madden NFL 19' Celebrates the Holidays With Nicki Minaj, SNL's Chris Redd, and More |url=https://comicbook.com/gaming/news/madden-nfl-19-nicki-minaj-snl-chris-redder/ |access-date=August 27, 2022 |website=Comicbook}}</ref><ref>{{Cite magazine |last=Rashed |first=Ayana |date=November 15, 2018 |title=Here's A New Madden NFL '19 Holiday Ad w/ Nicki Minaj & Chris Redd |url=https://respect-mag.com/2018/11/heres-a-new-madden-nfl-19-holiday-ad-w-nicki-minaj-chris-redd/ |url-status=dead |archive-url=https://web.archive.org/web/20220827194524/https://respect-mag.com/2018/11/heres-a-new-madden-nfl-19-holiday-ad-w-nicki-minaj-chris-redd/ |archive-date=August 27, 2022 |access-date=August 27, 2022 |magazine=[[Respect (magazine)|Respect]]}}</ref> and [[Diesel (brand)|Diesel]].<ref>{{Cite magazine |last=Chan |first=Tim |date=September 20, 2018 |title=Nicki Minaj Draws Heat for 'Anti-Bullying' Fashion Collab With Diesel |url=https://variety.com/2018/music/lifestyle/nicki-minaj-diesel-fashion-collaboration-controversy-1202949587/ |access-date=March 31, 2023 |magazine=Variety}}</ref>
In 2019, Minaj announced a collaboration with luxury fashion house [[Fendi]]. A capsule collection titled "Fendi Prints On", named after a lyric from her 2018 single '[[Chun-Li (song)|Chun-Li]]', was released in October.<ref>Multiple sources:
*{{Cite magazine |last=Leong |first=Abigail |date=September 30, 2019 |title=Prints On: Fendi X Nicki Minaj |url=https://www.lofficielsingapore.com/fashion/prints-on-fendi-x-nicki-minaj |access-date=August 18, 2022 |magazine=[[L'Officiel|L'Officiel Singapore]]}}
*{{Cite magazine |last=Penrose |first=Nerisha |date=October 14, 2019 |title=Nicki Minaj's New Fendi Prints On Collection Is Here |url=https://www.elle.com/fashion/shopping/a29458238/nicki-minaj-fendi-prints-on-shop/ |access-date=August 18, 2022 |magazine=[[Elle (magazine)|Elle]]}}
*{{Cite magazine |last=Rearick |first=Lauren |date=October 14, 2019 |title=Nicki Minaj Designed a Fendi Capsule Collection, and Wow, It's Bright |url=https://www.teenvogue.com/story/nicki-minaj-fendi-capsule-collection |access-date=August 18, 2022 |magazine=[[Teen Vogue]]}}
*{{Cite magazine |last=Murphy |first=Megumi |date=October 11, 2019 |title=Fendi Prints On X Nicki Minaj |url=https://flaunt.com/content/fendi-nicki-minaj |access-date=August 18, 2022 |magazine=[[Flaunt (magazine)|Flaunt]]}}
*{{Cite magazine |last=Arter |first=Neesha |date=October 16, 2019 |title=Nicki Minaj Celebrated Her 'Prints On' Capsule Collection for Fendi With a Very Pink Party |url=https://www.vogue.com/slideshow/nicki-minaj-prints-on-capsule-collection-party |access-date=August 18, 2022 |magazine=[[Vogue (magazine)|Vogue]]}}</ref>
In March 2022, Minaj fronted [[Marc Jacobs]]' "Heaven" spring collection.<ref>{{Cite magazine |last=Cowen |first=Trace William |date=March 4, 2022 |title=Nicki Minaj Showcases New Heaven by Marc Jacobs Collection |url=https://www.complex.com/style/nicki-minaj-showcases-new-heaven-by-marc-jacobs-collection |magazine=[[Complex Networks|Complex]] |access-date=March 6, 2022 |archive-date=March 6, 2022 |archive-url=https://web.archive.org/web/20220306061058/https://www.complex.com/style/nicki-minaj-showcases-new-heaven-by-marc-jacobs-collection |url-status=live}}</ref><ref>{{Cite magazine |last=Muhammad |first=Latifah |date=March 7, 2022 |title=Nicki Minaj Stars in Marc Jacobs Spring 2022 Campaign: Shop the Collection Before It Sells Out |url=https://www.billboard.com/culture/product-recommendations/nicki-minaj-marc-jacobs-heaven-campaign-1235040432/ |access-date=March 13, 2022 |magazine=[[Billboard (magazine)|Billboard]]}}</ref> In May, she was named the new creative director of American men's magazine ''[[Maxim (magazine)|Maxim]]'' and became the global ambassador for its sports betting brand, ''MaximBet''.<ref>{{Cite magazine |last=Rowley |first=Glenn |date=May 31, 2022 |title=Nicki Minaj Named 'Maxim' Creative Director & Global Ambassador of Sports Gambling Brand MaximBet |url=https://www.billboard.com/music/music-news/nicki-minaj-maximbet-global-ambassador-1235079378/ |magazine=[[Billboard (magazine)|Billboard]] |access-date=June 1, 2022 |archive-date=June 1, 2022 |archive-url=https://web.archive.org/web/20220601060102/https://www.billboard.com/music/music-news/nicki-minaj-maximbet-global-ambassador-1235079378/ |url-status=live}}</ref> In July, Minaj collaborated with Rap Snacks and released her own potato chip flavor, "Barbie-Que Honey Truffle".<ref>*{{Cite magazine |last=Weiss |first=Sabrina |date=June 30, 2022 |title=Nicki Minaj Debuts 'Barbie-Que' Chips with Rap Snacks Ahead of Headlining Spot at ESSENCE Festival |url=https://people.com/food/nicki-minaj-debuts-barbie-que-chips-with-rap-snacks-ahead-of-headlining-spot-at-essence-festival/ |access-date=March 31, 2023 |magazine=People}}
*{{Cite magazine |last=Griffin |first=Marc |date=July 2, 2022 |title=Nicki Minaj Collaborates With Rap Snacks For Signature "Barbie-Que" Flavor |url=https://www.vibe.com/news/business/nicki-minaj-rap-snacks-barbie-que-flavor-1234673157/ |access-date=March 31, 2023 |website=[[Vibe (magazine)|Vibe]]}}</ref> The following month, Mattel sued Rap Snacks, claiming the company violated the Barbie trademark and had made a "deliberate and calculated" choice to do so. The lawsuit was settled in September after the company changed the snack's name to "Bar-B-Quin".<ref>{{Cite magazine |last=Donahue |first=Bill |date=September 14, 2022 |title=Mattel Drops Lawsuit Over Nicki Minaj-Branded 'Barbie-Que' Chips |url=https://www.billboard.com/pro/nicki-minaj-rap-snacks-mattel-lawsuit-dropped/ |url-access=limited |access-date=March 31, 2023 |magazine=Billboard}}</ref> In July 2023, a playable character model of Minaj was released as [[downloadable content]] for [[Call of Duty: Modern Warfare II (2022 video game)|Call of Duty: Modern Warfare II]], marking the first female "operator" of the game.<ref>{{Cite news |last=Louallen |first=Doc |date=July 27, 2023 |title=Nicki Minaj is coming to Call of Duty as first female Operator |work=[[USA Today]] |url=https://www.usatoday.com/story/tech/gaming/2023/07/27/nicki-minaj-call-of-duty-character-first-female-operator/70482414007/ |access-date=November 10, 2023}}</ref> In December 2023, she and Roblox introduced the virtual world Gag City on the game platform to promote her fifth album, ''[[Pink Friday 2]]'' (2023).<ref>{{Cite tweet |number=1733265550909067509 |user=DavidBaszucki |title=Happy birthday @NICKIMINAJ and excited to have you on Roblox. Gag City is awesome... |first=David |last=Baszucki |author-link=David Baszucki |date=December 8, 2023 |access-date=December 9, 2023}}</ref><ref>{{Cite web |last=Kemp |first=Chadley |date=December 8, 2023 |title=Nicki Minaj Joins Roblox for 'Pink Friday 2' Album Launch |url=https://www.ginx.tv/en/roblox/nicki-minaj |access-date=December 9, 2023 |publisher=GINX TV}}</ref>
===Products===
[[File:Nicki Minaj perfume launch (cropped).jpg|thumb|upright|Minaj promoting her first fragrance "Pink Friday" in 2012|alt=]]
Minaj launched her [[Fragrance compound|fragrance]] line in September 2012 with "Pink Friday",<ref>{{cite news|title=Nicki Minaj, maybe 'Idol' judge, unveils new fragrance|url=http://marquee.blogs.cnn.com/2012/08/21/nicki-minaj-maybe-idol-judge-unveils-new-fragrance/|work=[[CNN]] Entertainment|publisher=[[Turner Broadcasting System, Inc.]]|access-date=August 21, 2012|archive-date=August 22, 2012|archive-url=https://web.archive.org/web/20120822053833/http://marquee.blogs.cnn.com/2012/08/21/nicki-minaj-maybe-idol-judge-unveils-new-fragrance/|url-status=dead}}</ref> which received three [[FiFi Awards|FiFi Award]] nominations.<ref>{{Cite news|url=http://www.instyle.com/news/2013-fifi-finalists-announced-which-celebrity-fragrances-should-win-award|title=2013 FiFi Finalists Announced! Which Celebrity Fragrances Should Win an Award?|work=InStyle.com|access-date=March 9, 2018|language=en|archive-date=March 9, 2018|archive-url=https://web.archive.org/web/20180309120519/http://www.instyle.com/news/2013-fifi-finalists-announced-which-celebrity-fragrances-should-win-award|url-status=dead}}</ref> She has since released ten fragrances, with many named after her album releases.
In March 2024, Minaj launched her [[Artificial nails|press-on nails]] line, Pink Friday Nails.<ref>{{Cite magazine |last=Johnston |first=Rylee |date=March 15, 2024 |title=Nicki Minaj Launches Her Press-On Nail Brand & We Tried It: Here's What to Know About Pink Friday Nails |url=https://www.billboard.com/culture/product-recommendations/nicki-minaj-pink-friday-nails-buy-online-1235632607/ |access-date=March 26, 2024 |magazine=Billboard}}</ref> [[Pop-up retail|Pop-up]] stations dedicated to the range appeared on the Pink Friday 2 World Tour.<ref>{{Cite press release |date=March 15, 2024 |title=Nicki Minaj launches innovative new nail brand Pink Friday Nails |url=https://www.prnewswire.com/news-releases/nicki-minaj-launches-innovative-new-nail-brand-pink-friday-nails-302089903.html |access-date=March 26, 2024 |agency=[[PR Newswire]] |type=Press release}}</ref>
== Public image ==
[[File:Christopher Macsurak Nicki Minaj cropped.jpg|thumb|upright|Early in her career, Minaj often wore colorful costumes, cosmetics, and wigs.|alt=An Afro-American woman in a blonde wig and bright teal eyeshadow wears a shirt constructed of variously colored cotton balls]]
''Billboard'' listed Minaj as the fourth-most-active musician on social media on its March 2011 [[List of Billboard Social 50 number-one artists|Social 50]] chart.<ref>{{cite magazine|url={{BillboardURLbyName|artist=nicki minaj|chart=Social 50}}|title=Nick Minaj – Chart History|magazine=Billboard|access-date=August 20, 2012}}</ref> Minaj has been called as a "fashion icon" by several publications, and was called a "camp style icon" by [[Refinery29]].<ref>{{Cite magazine|title=In Search of the Real Nicki Minaj|url=https://time.com/3696884/in-search-of-the-real-nicki-minaj/|access-date=November 12, 2021|magazine=Time|language=en|archive-date=February 10, 2022|archive-url=https://web.archive.org/web/20220210122900/https://time.com/3696884/in-search-of-the-real-nicki-minaj/|url-status=dead}}</ref><ref>{{Cite web|date=May 28, 2021|title=Nicki Minaj's Iconic Barbie Pink Hair Is Back and Better (and Longer) Than Ever|url=https://www.allure.com/story/nicki-minaj-barbie-pink-hair-color|access-date=September 3, 2021|website=Allure|language=en-US}}</ref><ref>{{Cite web|last=III|first=Robby Seabrook|title=Here's a Look at the Freshest Rappers of the Modern Era|url=https://www.xxlmag.com/freshest-rappers-of-the-modern-era/|access-date=November 12, 2021|website=XXL Mag|date=October 20, 2020 |language=en}}</ref><ref>{{Cite web|title=Nicki Minaj Doesn't Understand Camp But It's Not Her Fault|url=https://www.refinery29.com/en-us/2019/05/232183/nicki-minaj-met-gala-camp-photo|access-date=May 10, 2021|website=Refinery29}}</ref> [[BET]] named Minaj a "gay icon".<ref>{{Cite web|title=Gay Icons|url=https://www.bet.com/content/bet/celebrities/photos/2011/06/gay-icons-.html?cid=facebook|access-date=August 2, 2021|website=BET}}{{dead link|date=July 2024|bot=medic}}{{cbignore|bot=medic}}</ref> In 2011, Minaj cited [[Alexander McQueen]], [[Gianni Versace]], and [[Christian Louboutin]] as her favorite designers.<ref>{{cite web|url=http://www.teenvogue.com/celebrity-style/2011-04/nicki-minaj-interview|title=Nicki Minaj Interview – Celebrity Style|date=April 17, 2011|work=Teen Vogue|access-date=August 14, 2012}}</ref> ''[[The Huffington Post]]'' described her style as "risk-taking" and "far-out", with "bold sartorial choices".<ref>{{cite news|url=https://www.huffingtonpost.com/2011/10/05/nicki-minajs-most-memorab_n_996807.html|title=Nicki Minaj's Most Memorable Moments|last=Gregoire|first=Carolyn|date=October 5, 2011|work=HuffPost|access-date=August 14, 2012}}</ref> In 2014, Minaj reinvented her image, sporting a "natural" and "softer" look, wearing fewer wigs and less colourful costumes.<ref name="huffpostnm12"/> She stated that she "went so far to the other side that there's only one place to go from there. You can either continue doing costumes or you can just say, "Hey guess what? This will shock them even more. Doing nothing will shock them even more"".<ref name="huffpostnm12">{{Cite web|url=https://www.huffpost.com/entry/nicki-minaj-natural-hair-makeup_n_5197794|title=The Real Reason Nicki Minaj Has Gone For A More 'Natural' Look|access-date=July 6, 2021|website=Huff Post|date=April 23, 2014}}</ref><ref>{{Cite web|url=https://www.cosmopolitan.com/entertainment/news/a23938/nicki-minaj-is-done-wearing-crazy-wigs/|title=Nicki Minaj Isn't Bringing Her Crazy Wigs Back Anytime Soon|access-date=July 6, 2021|website=Cosmpolitan|date=April 21, 2014}}</ref>
In 2010, Minaj said that although she originally felt obligated to mimic the provocative behavior of the "female rappers of [her] day", she intended to subdue her sexuality because she "[wants] people—especially young girls—to know that in life, nothing is going to be based on sex appeal. You've got to have something else to go with that."<ref>{{cite web|url=http://www.vibe.com/article/nicki-minaj-friends-cover-vibe-magazine|title=Nicki Minaj & Friends Cover Vibe Magazine|date=June 2, 2010|website=Vibe|publisher=Vibe Media|access-date=November 21, 2013}}</ref><ref>{{cite web|url=http://www.interviewmagazine.com/music/nicki-minaj/|title=Nicki Minaj|author=Rachel, T. Cole|date=April 2010|website=Interview|access-date=July 19, 2010}}</ref> Her physique, notably her [[buttocks]], attracted attention from the media.<ref>{{cite news|url=https://www.hollywoodreporter.com/earshot/nicki-minaj-starships-video-hawaii-317581|title=Nicki Minaj Premieres Sexy 'Starships' Clip|last=Medina|first=Portia|date=April 27, 2012|work=The Hollywood Reporter|access-date=May 19, 2012}}</ref><ref>{{cite web|url=https://www.maxim.com/women-az/nicki-minaj-profile|title=Nicki Minaj|website=Maxim|language=en-us|access-date=October 4, 2018|archive-date=October 5, 2018|archive-url=https://web.archive.org/web/20181005033347/https://www.maxim.com/women-az/nicki-minaj-profile|url-status=dead}}</ref> The cover art and music video for her 2014 single, "[[Anaconda (Nicki Minaj song)|Anaconda]]", attracted significant media attention upon release.<ref name=":6">{{cite news|url=https://www.autostraddle.com/nicki-minajs-feminism-isnt-about-your-comfort-zone-on-anaconda-and-respectability-politics-251866/|title=Nicki Minaj's Feminism Isn't About Your Comfort Zone: On "Anaconda" and Respectability Politics|date=August 25, 2014|work=Autostraddle|access-date=October 26, 2018|language=en-US}}</ref> ''[[The Guardian]]'' wrote that she "doesn't shy from ruffling her audience's feathers",<ref>{{cite web|url=https://www.theguardian.com/music/musicblog/2014/jul/25/nicki-minaj-anaconda-cover-too-sexy-instagram|title=Nicki Minaj's Anaconda cover artwork: too racy for its own good?|last=Mokoena|first=Tshepo|date=July 25, 2014|website=The Guardian|language=en|access-date=October 26, 2018}}</ref> and others praised Minaj for "owning her sexuality",<ref>{{Cite news|url=https://jezebel.com/nicki-minaj-doesnt-need-your-advice-or-input-1612258207|title=Nicki Minaj Doesn't Need Your Advice or Input|last=Brown|first=Kara|work=Jezebel|access-date=October 26, 2018|language=en-US|archive-date=August 25, 2019|archive-url=https://web.archive.org/web/20190825152120/https://jezebel.com/nicki-minaj-doesnt-need-your-advice-or-input-1612258207|url-status=dead}}</ref> examining her work through a [[Feminism|feminist]] perspective.<ref name=":6" /><ref>{{cite journal|last=Halliday|first=Aria S|year=2017|title=Envisioning Black Girl Futures Nicki Minaj's Anaconda Feminism and New Understandings of Black Girl Sexuality in Popular Culture|url=https://www.academia.edu/34793129|language=en|journal=Departures in Critical Qualitative Research|volume=6|issue=3|pages=65–77|doi=10.1525/dcqr.2017.6.3.65|access-date=June 9, 2020|issn=2333-9489|archive-date=March 16, 2022|archive-url=https://web.archive.org/web/20220316012046/https://www.academia.edu/34793129|url-status=dead}}</ref><ref>{{cite web|url=https://mic.com/articles/95520/nicki-minaj-s-anaconda-is-the-fiercest-take-on-female-sexuality-of-the-year#.NYRbcp9fP|title=Nicki Minaj's 'Anaconda' Is the Fiercest Take on Female Sexuality of the Year|date=August 4, 2014|last=Clifton|first=Derrick|publisher=[[Mic (media company)|Mic]]|access-date=June 9, 2020|language=en}}</ref><ref>{{Cite web|url=https://www.theyoungfolks.com/review/38651/nicki-minaj-feminism-and-the-message-behind-anaconda/|title=Nicki Minaj, Feminism and the Message Behind 'Anaconda' {{!}} The Young Folks|website=www.theyoungfolks.com|date=August 30, 2014|language=en-US|access-date=October 26, 2018}}</ref><ref>{{Cite web|url=https://www.theguardian.com/lifeandstyle/womens-blog/2014/aug/22/nicki-minaj-anaconda-video-black-women-sexuality|title=Shocked and outraged by Nicki Minaj's Anaconda video? Perhaps you should butt out|last=Lhooq|first=Michelle|date=August 23, 2014|website=The Guardian|language=en|access-date=October 26, 2018}}</ref>
Minaj spoke about feminism in an interview with ''[[Vogue (magazine)|Vogue]]'' in 2015, saying "There are things that I do that feminists don't like, and there are things that I do that they do like. I don't label myself. I just say the truth about what I feel. I feel like women can do anything that they put their minds to."<ref>{{cite web|url=http://www.vogue.com/10650415/nicki-minaj-interview-feminism-pinkprint|title=Newly Single Nicki Minaj on Feminism, Meek Mill, and Rapping at 50|last1=Frank|first1=Alex|website=Vogue|date=February 12, 2015|access-date=April 8, 2015|archive-date=June 26, 2015|archive-url=https://web.archive.org/web/20150626173115/http://www.vogue.com/10650415/nicki-minaj-interview-feminism-pinkprint|url-status=dead}}</ref> Her outspoken views have received significant media attention.<ref>{{Cite news |date=October 14, 2022 |title=Nicki Minaj's swipe at Grammys for changing song category pulls Latto into Twitter feud |url=https://www.latimes.com/entertainment-arts/music/story/2022-10-14/nicki-minaj-latto-twitter-feud-grammys-decision |url-status=live |archive-url=https://web.archive.org/web/20221217112355/https://www.latimes.com/entertainment-arts/music/story/2022-10-14/nicki-minaj-latto-twitter-feud-grammys-decision |archive-date=December 17, 2022 |access-date=December 1, 2022 |newspaper=[[Los Angeles Times]]}}</ref><ref>{{Cite magazine |last1=Elibert |first1=Mark |date=September 13, 2018 |title=From Cardi B to Remy Ma, Here's a Look at All of Nicki Minaj's Beefs |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-feuds-timeline-cardi-b-remy-ma-mariah-carey-8474813/ |url-status=live |archive-url=https://web.archive.org/web/20221205150505/https://www.billboard.com/music/rb-hip-hop/nicki-minaj-feuds-timeline-cardi-b-remy-ma-mariah-carey-8474813/ |archive-date=December 5, 2022 |access-date=December 1, 2022 |magazine=Billboard}}</ref> In a 2018 interview, she discussed [[sex worker]]s, her own sex appeal, and sexuality in music and on social media, which attracted criticism.<ref>{{Cite web|url=https://www.complex.com/music/2018/06/nicki-minaj-responds-critics-who-call-her-lyrics-hypocritical|title=Nicki Minaj Goes Off on Critics Who Claim Her Lyrics Are 'Hypocritical'|website=Complex|date=June 17, 2018 |language=en|access-date=October 26, 2018}}</ref><ref>{{Cite news|url=https://www.vibe.com/2018/06/nicki-minaj-responds-sexual-lyrics-elle-interview/|title=Nicki Minaj Responds To Claims That Her Sexual Lyrics Are Hypocritical|date=June 17, 2018|work=Vibe|access-date=October 26, 2018}}</ref> Vanessa Grigoriadis of ''[[The New York Times Magazine]]'' wrote that Minaj "has become expert at modelling the ways that women can wield power in the industry. But she has also drawn attention to the ways in which power can be embodied by a woman standing up for herself and speaking her own mind."<ref name="NYT123"/> Minaj has also been involved in [[Nicki Minaj–Cardi B feud|a widely publicized feud]] with rapper [[Cardi B]] since around 2017.<ref>{{cite magazine |last1=Saponara |first1=Michael |last2=Diaz |first2=Angel |title=A Timeline of Nicki Minaj & Cardi B's Relationship |url=https://www.billboard.com/lists/nicki-minaj-cardi-b-timeline/ |magazine=[[Billboard (magazine)|Billboard]] |access-date=October 23, 2025 |date=October 1, 2025}}</ref><ref>{{Cite magazine|title=Nicki Minaj Opens Up About Cardi B Altercation, Plus More Revelations From Queen Radio Episode 8|magazine=Billboard|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-queen-radio-show-cardi-b-fashion-week-fight-8474543/|access-date=January 25, 2025|date=September 10, 2018|archive-url=https://web.archive.org/web/20230405223036/https://www.billboard.com/music/rb-hip-hop/nicki-minaj-queen-radio-show-cardi-b-fashion-week-fight-8474543/|archive-date=April 5, 2023|url-status=live}}</ref>
== Personal life ==
In a 2014 interview, Minaj stated that she had an abortion as a teenager, which she referenced in her songs "[[Sucka Free|Autobiography]]" and "[[All Things Go]]". She said that it was the hardest thing she had gone through, and although it "haunted" her she stands by her decision as she "didn't have anything to offer a child" and she is pro-choice.<ref name="nicminab">{{cite magazine |last=Ramirez |first=Erika |date=December 31, 2014 |title=Nicki Minaj Opens Up About Her Abortion: 'I Thought I Was Going to Die' |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-abortion-interview-6422438/ |url-status=live |archive-url=https://web.archive.org/web/20190731065813/https://www.billboard.com/articles/columns/the-juice/6422438/nicki-minaj-abortion-interview |archive-date=July 31, 2019 |access-date=May 1, 2019 |magazine=Billboard}}</ref>
Minaj dated her [[hype man]] [[Safaree Samuels]] from 2003 to 2014.<ref>{{cite magazine|url=https://www.rollingstone.com/music/videos/nicki-minaj-breakup-inspired-pinkprint-movie-20141220|title=Watch Nicki Minaj's Breakup-Inspired 'Pinkprint Movie'|last=Kreps|first=Daniel|magazine=Rolling Stone|access-date=December 20, 2014}}</ref> She dated rapper [[Meek Mill]] from 2015 until January 2017.<ref>{{cite web|url=http://www.vibe.com/2015/04/meek-mill-confirms-relationship-with-nicki-minaj/|title=Meek Mill Confirms Relationship With Nicki Minaj|website=Vibe|date=April 10, 2015|archive-url=https://web.archive.org/web/20230515165928/https://www.vibe.com/music/music-news/meek-mill-confirms-relationship-with-nicki-minaj-340227/|archive-date=May 15, 2023|url-status=live}}</ref><ref>{{cite web|url=http://people.com/music/nicki-minaj-meek-mill-break-up-tweet/|title=Nicki Minaj and Meek Mill Split After Nearly 2 Years of Dating|access-date=May 28, 2018|archive-url=https://web.archive.org/web/20230228021902/https://people.com/music/nicki-minaj-meek-mill-break-up-tweet/|archive-date=February 28, 2023|url-status=live}}</ref> In December 2018, Minaj began dating a childhood friend, Kenneth Petty. She announced their marriage in October 2019, hyphenating his last name to hers.<ref>{{Cite magazine |last=Bailey |first=Alyssa |date=October 22, 2019 |title=Nicki Minaj Announces She Married Kenneth Petty |url=https://www.elle.com/culture/celebrities/a29545963/nicki-minaj-kenneth-petty-married/ |website=ELLE |access-date=March 8, 2022 |archive-date=March 8, 2022 |archive-url=https://web.archive.org/web/20220308212633/https://www.elle.com/culture/celebrities/a29545963/nicki-minaj-kenneth-petty-married/ |url-status=live }}</ref><ref>{{cite magazine |last=Kaufman |first=Gil |date=October 22, 2019 |title=Nicki Minaj Announces She's Married to Kenneth 'Zoo' Petty |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-announces-marriage-kenneth-petty-8533724/ |magazine=[[Billboard (magazine)|Billboard]] |access-date=October 22, 2019 |archive-date=October 22, 2019 |archive-url=https://web.archive.org/web/20191022184321/https://www.billboard.com/amp/articles/columns/hip-hop/8533724/nicki-minaj-announces-marriage-kenneth-petty |url-status=live }}</ref><ref>{{cite magazine | url=https://www.billboard.com/music/rb-hip-hop/who-did-nicki-minaj-marry-kenneth-petty-8533743/ | title=5 Things to Know About Kenneth Petty, Nicki Minaj's Husband | magazine=[[Billboard (magazine)|Billboard]]|date=June 12, 2020|access-date=September 14, 2024}}</ref> In July 2020, she announced that she was expecting her first child.<ref>{{cite magazine |last=Kaufman |first=Gil |date=July 20, 2020 |title=Nicki Minaj Is Pregnant: See the Announcement |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-pregnant-baby-bump-photo-9421072/ |magazine=[[Billboard (magazine)|Billboard]] |access-date=July 20, 2020 |archive-date=July 21, 2020 |archive-url=https://web.archive.org/web/20200721123329/https://www.billboard.com/articles/columns/hip-hop/9421072/nicki-minaj-pregnant-baby-bump-photo |url-status=live }}</ref> She gave birth to a son on September 30, 2020, whom she refers to publicly as Papa Bear.<ref>{{Cite magazine |last=Weaver |first=Hilary |date=May 31, 2021 |title=Nicki Minaj Shared a Rare Video of Her Baby Boy in an Adorable Burberry Teddy Bear Outfit |url=https://www.elle.com/culture/celebrities/a36578833/nicki-minaj-son-burberry-teddy-bear-outfit/ |magazine=[[Elle (magazine)|Elle]] |access-date=August 14, 2022 |archive-date=August 26, 2022 |archive-url=https://web.archive.org/web/20220826134539/https://www.elle.com/culture/celebrities/a36578833/nicki-minaj-son-burberry-teddy-bear-outfit/ |url-status=live }}</ref>
In 2011, her cousin Nicholas Telemaque was murdered near his home in [[Brooklyn]], an incident she referenced in "All Things Go" and "[[Champion (Nicki Minaj song)|Champion]]".<ref>{{cite news |title=Nicki Minaj Mourns Cousin Nicholas Telemaque's Death |url=http://www.mtv.com/news/articles/1666795/nicki-minaj-cousin-murder.jhtml |access-date=April 7, 2012 |publisher=[[MTV News]] |archive-date=March 2, 2014 |archive-url=https://web.archive.org/web/20140302223429/http://www.mtv.com/news/articles/1666795/nicki-minaj-cousin-murder.jhtml |url-status=dead}}</ref> Her father, Robert Maraj, was killed in a 2021 [[Hit and run|hit-and-run]] accident in [[Long Island]].<ref name="father's death" /><ref>{{Cite news |last=Byrne |first=Suzy |date=May 15, 2021 |title=Nicki Minaj breaks silence on father's 'devastating' hit-and-run death |language=en-US |publisher=[[Yahoo! News]] |url=https://www.yahoo.com/entertainment/nicki-minaj-breaks-silence-on-fathers-hit-and-run-death-170107444.html |access-date=May 15, 2021 |quote="Tho I can't really bring myself to discuss the passing of my father as yet," she wrote in a letter to fans posted on her website Friday, "I can say it has been the most devastating loss of my life." |archive-date=May 15, 2021 |archive-url=https://web.archive.org/web/20210515155555/https://www.yahoo.com/entertainment/nicki-minaj-breaks-silence-on-fathers-hit-and-run-death-170107444.html |url-status=live }}</ref> Charles Polevich, a 70-year-old man, was charged with leaving the scene of incident involving death of a person and tampering with or suppressing physical evidence.<ref name="bbc news nickis father dead">{{cite news |date=February 18, 2021 |title=Man charged after Nicki Minaj's father killed in hit-and-run |website=[[BBC News]] |url=https://www.bbc.co.uk/news/newsbeat-56067567 |access-date=February 24, 2021 |archive-date=February 22, 2021 |archive-url=https://web.archive.org/web/20210222170605/https://www.bbc.co.uk/news/newsbeat-56067567 |url-status=live }}</ref>
In September 2021, Minaj responded to a fan's question about her lack of appearances at public events, stating that she did not want to put her infant at risk due to the [[COVID-19 pandemic]], and that she had tested positive for the virus and had to quarantine from her son. She said she was getting the [[COVID-19 vaccine]] after she felt she had done enough research, and she would be vaccinated before she went on tour.<ref>{{cite web|title=Nicki Minaj Reveals She Is Not Attending 2021 Met Gala Due to Vaccination Requirement|url=https://www.hollywoodreporter.com/news/music-news/nicki-minaj-not-attending-2021-met-gala-1235013121/|work=[[The Hollywood Reporter]]|date=September 13, 2021|access-date=October 9, 2024|archive-url=https://web.archive.org/web/20221015193935/https://www.hollywoodreporter.com/news/music-news/nicki-minaj-not-attending-2021-met-gala-1235013121/|archive-date=October 15, 2022|url-status=live}}</ref><ref>{{Citation |title=Nicki Minaj Interview: Mom Guilt, COVID-19 & Having To Quarantine From Her Son! | date=February 2, 2022 |url=https://www.youtube.com/watch?v=2J3KzyBXBrI |access-date=February 3, 2022 |publisher=[[WLLD]]|archive-url=https://web.archive.org/web/20230713045804/https://www.youtube.com/watch?v=2J3KzyBXBrI|archive-date=July 13, 2023|url-status=live}}</ref> She had also tweeted that her cousin in Trinidad told her that his friend suffered swollen testicles and became impotent after taking the vaccine, a claim that received backlash and was denied by Trinidad and Tobago health minister [[Terrence Deyalsingh]], who stated that there was no evidence of that.<ref>{{Cite news |title=Nicki Minaj says she didn't attend Met Gala because of Covid vaccination requirement |url=https://www.nbcnews.com/pop-culture/celebrity/nicki-minaj-says-she-won-t-attend-met-gala-because-n1279088|date=September 13, 2021 |access-date=September 15, 2021 |publisher=NBC News|archive-url=https://web.archive.org/web/20230403193529/https://www.nbcnews.com/pop-culture/celebrity/nicki-minaj-says-she-won-t-attend-met-gala-because-n1279088|archive-date=April 3, 2023|url-status=live}}</ref> In May 2022, Olivia Truffaut-Wong of ''[[New York (magazine)#The Cut|The Cut]]'' stated that Minaj had likely gotten vaccinated as the 2022 Met Gala (which Minaj attended) required [[Vaccine passports during the COVID-19 pandemic|proof of vaccination]] and a negative [[COVID-19 testing|COVID-19 PCR test]] to attend.<ref>{{Cite magazine |last=Truffaut-Wong |first=Olivia |date=May 2, 2022 |title=So ... Does This Mean Nicki Minaj Got Vaccinated? |url=https://www.thecut.com/2022/05/nicki-minaj-2022-met-gala-vaccine.html |access-date=May 4, 2022 |magazine=[[New York (magazine)#The Cut|The Cut]] |archive-url=https://web.archive.org/web/20230323050116/https://www.thecut.com/2022/05/nicki-minaj-2022-met-gala-vaccine.html|archive-date=March 23, 2023|url-status=live}}</ref>
Minaj stated that she is a [[Christian]].<ref>{{Cite tweet |user=NickiMinaj |number=1751444694574481759 |title=All fakks. Yes TRINI KING!!!! I AM a Christian, tho. But let them know babes.}}</ref> In a 2022 post on Twitter, Minaj said she has [[ADHD]]; in March 2026 she posted about being [[neurodivergent]].<ref>{{cite web|url=https://www.grammy.com/news/exploring-the-links-between-adhd-and-hip-hop-nicki-minaj-kanye-west-joyner-lucas|title="I Made My ADHD Into My Strength": Understanding The Link Between Rap & Neurodivergence|publisher=The Recording Academy|date=August 3, 2022|access-date=March 18, 2026}}</ref><ref>{{Cite web |last=Oatman |first=Alexis |date=March 14, 2026|title=Nicki Minaj Reveals That She's Neurodivergent |url=https://www.hot97.com/news/nicki-minaj-reveals-that-shes-neurodivergent/ |access-date=March 15, 2026|publisher=HOT 97}}</ref> A long-time [[legal permanent resident]] of the U.S, she said in 2024 that she does not have U.S citizenship.<ref>{{Cite magazine |last=Cho |first=Regina |date=September 19, 2024 |title=Nicki Minaj Reveals Surprising Information About Her Current Immigration Status |url=https://www.vibe.com/news/entertainment/nicki-minaj-not-citizen-us-immigration-status-1234924589/ |access-date=September 25, 2024 |magazine=[[Vibe (magazine)|Vibe]]}}</ref><ref name="resident"/> In January 2026, Minaj stated she was finalizing her citizenship paperwork following a joint public appearance with President [[Donald Trump]].<ref name="resident">{{cite web|url=https://www.nytimes.com/2026/01/29/us/politics/trump-nicki-minaj-visa.html|title=Did Trump Really Give Nicki Minaj a $1 Million 'Gold Card' Visa?|work=[[The New York Times]]|date=January 29, 2026|access-date=January 30, 2026|archive-url=https://web.archive.org/web/20260130012713/https://www.nytimes.com/2026/01/29/us/politics/trump-nicki-minaj-visa.html|archive-date=January 30, 2026|url-status=bot: unknown|last=Kanno-Youngs|first=Zolan}}</ref><ref>{{cite web|url=https://thehill.com/blogs/in-the-know/5712660-nicki-minaj-donald-trump-gold-card/|title=Nicki Minaj receives 'Gold Card,' White House says it's a 'memento'|work=The Hill|date=January 20, 2026|access-date=January 20, 2026|archive-url=https://web.archive.org/web/20260130150918/https://thehill.com/blogs/in-the-know/5712660-nicki-minaj-donald-trump-gold-card/|archive-date=January 30, 2026|url-status=live|last=Rego|first=Max}}</ref>
== Legal issues ==
Minaj's husband Kenneth Petty was convicted of attempted rape at age 16 in 1995, serving four years in prison and registering as a [[sex offender]] upon release.<ref name="USA">{{Cite web|last=Henderson|first=Cydney|title=Nicki Minaj's husband, Kenneth Petty, arrested for failing to register as a sex offender|url=https://www.usatoday.com/story/entertainment/celebrities/2020/03/04/nicki-minajs-husband-arrested-failing-register-sex-offender/4956888002/|access-date=September 8, 2021|website=[[USA Today]]|date=March 4, 2020|archive-url=https://web.archive.org/web/20230713045132/https://www.usatoday.com/story/entertainment/celebrities/2020/03/04/nicki-minajs-husband-arrested-failing-register-sex-offender/4956888002/|archive-date=July 13, 2023|url-status=live}}</ref> When Minaj and her husband moved to California, he failed to update his residence on the sex offender registration and was indicted in February 2020; he registered in the state's database by the following month.<ref>{{cite magazine|last=Aniftos|first=Rania|date=March 9, 2020|title=Nicki Minaj's Husband Kenneth Petty Registers as Sex Offender in California|magazine=Billboard|url=https://www.billboard.com/pro/nicki-minaj-husband-kenneth-petty-registers-sex-offender-california/|url-access=subscription|access-date=June 9, 2020|archive-url=https://web.archive.org/web/20240124233530/https://www.billboard.com/pro/nicki-minaj-husband-kenneth-petty-registers-sex-offender-california/|archive-date=January 24, 2024|url-status=live}}</ref> In August 2021, Jennifer Hough, the woman who accused Petty when they were both 16, filed a lawsuit against the couple alleging harassment, intimidation and infliction of emotional distress, and requested a $20 million [[default judgement]] when Minaj did not respond to the lawsuit in time;<ref>{{Cite magazine|last=Kreps|first=Daniel|date=October 14, 2021|title=Nicki Minaj, Kenneth Petty Sued Over Intimidation of Sexual Assault Victim|url=https://www.rollingstone.com/music/music-news/nicki-minaj-kenneth-petty-lawsuit-intimidation-sexual-assault-victim-1212205/|magazine=Rolling Stone|access-date=August 14, 2021}}</ref><ref>{{cite web|url=https://www.thedailybeast.com/nicki-minaj-finally-responds-to-jennifer-houghs-witness-intimidation-lawsuit-sort-of|title=Nicki Minaj Finally Responds to Jennifer Hough's Witness-Intimidation Lawsuit—Sort Of|work=[[The Daily Beast]]|date=October 14, 2021|access-date=September 14, 2024|archive-url=https://web.archive.org/web/20230603081237/https://www.thedailybeast.com/nicki-minaj-finally-responds-to-jennifer-houghs-witness-intimidation-lawsuit-sort-of|archive-date=June 3, 2023|url-status=live}}</ref> the court declined it, declaring that Minaj was not in default.<ref name="case RS 21">{{cite magazine|url=https://www.rollingstone.com/music/music-news/nicki-minaj-lawsuit-didnt-default-jennifer-hough-1244625/|title=Nicki Minaj Avoids $20 Million Default Judgment in Harassment Lawsuit|magazine=Rolling Stone|date=October 19, 2021|access-date=September 14, 2024|archive-url=https://web.archive.org/web/20221210015659/https://www.rollingstone.com/music/music-news/nicki-minaj-lawsuit-didnt-default-jennifer-hough-1244625/|archive-date=December 10, 2022|url-status=live}}</ref> Minaj filed a response, calling the allegations "blatant falsehoods" and including evidence of discrepancies in Hough's story.<ref>{{cite web|url=https://www.complex.com/music/nicki-minaj-responds-husbands-accusers-harassment-suit|title=Nicki Minaj Responds to Husband's Accuser's Harassment Suit|work=Complex|date=December 9, 2021|archive-url=https://web.archive.org/web/20221206190851/https://www.complex.com/music/nicki-minaj-responds-husbands-accusers-harassment-suit|archive-date=December 6, 2022|url-status=live}}</ref> Minaj stated that she "never asked [Hough] to change her story", "never offered her any money in return for a statement", and "did not threaten her with any type of harm". She stated that, after her husband's indictment, Hough contacted her via a mutual acquaintance, and in their phone call, indirectly asked Minaj for a payoff, "mistakenly believ[ing] that I would pay her money to take back her accusation against my husband". Minaj said she responded by telling Hough that "the indictment was my husband's problem, not mine, and that he would have to live with the consequences of having chosen years ago to plead guilty to a crime which he did not commit", and that Hough said "I'm not saying it didn't happen, but maybe it was just a misunderstanding".<ref>{{Cite web|title=Nicki Minaj Reportedly Comments on Bribery Allegations Against Her and Husband in Harassment Case|url=https://www.complex.com/music/nicki-minaj-responds-bribery-allegations-harassment-case-nicole-hough|website=Complex|date=January 4, 2022|access-date=September 14, 2024|archive-url=https://web.archive.org/web/20221206184508/https://www.complex.com/music/nicki-minaj-responds-bribery-allegations-harassment-case-nicole-hough|archive-date=December 6, 2022|url-status=live}}</ref><ref>{{cite web|url=https://allhiphop.com/news/nicki-minaj-bloods-jennifer-hough-kenneth-petty-misunderstanding/|title=EXCLUSIVE: Nicki Minaj Denies Being In The Bloods, Says Husband's Victim Called Rape A "Misunderstanding"|work=[[AllHipHop]]|date=January 2, 2022|access-date=September 14, 2024|archive-url=https://web.archive.org/web/20221206184527/https://allhiphop.com/news/nicki-minaj-bloods-jennifer-hough-kenneth-petty-misunderstanding/|archive-date=December 6, 2022|url-status=live}}</ref> Hough voluntarily dismissed the case against Minaj.<ref>{{Cite magazine|last=Donahue|first=Bill|date=2022-01-12|title=Nicki Minaj Dropped From Harassment Lawsuit Filed by Husband's Accuser|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-dropped-harassment-lawsuit-husband-1235018452/|access-date=2022-01-13|magazine=Billboard|archive-url=https://web.archive.org/web/20240124233530/https://www.billboard.com/business/legal/nicki-minaj-dropped-harassment-lawsuit-husband-1235018452/|archive-date=January 24, 2024|url-status=live}}</ref>
On May 25, 2024, Minaj was detained when [[marijuana]] was found in her luggage at [[Schiphol Airport]] in [[Amsterdam, Netherlands]], where she was boarding a plane to perform [[Pink Friday 2 World Tour|a concert]] in [[Manchester, England]]. She was fined and released the same day.<ref name=arrestandpostponed>{{cite news|url=https://www.nbcnews.com/pop-culture/pop-culture-news/nicki-minaj-apparently-arrested-netherlands-incident-involving-luggage-rcna154065|title=Nicki Minaj released after apparent arrest in the Netherlands on suspicion of exporting soft drugs|first=Minyvonne|last=Burke|publisher=[[NBC News]]|date=May 25, 2024|access-date=May 25, 2024|archive-url=https://web.archive.org/web/20240608110225/https://www.nbcnews.com/pop-culture/pop-culture-news/nicki-minaj-apparently-arrested-netherlands-incident-involving-luggage-rcna154065|archive-date=June 8, 2024|url-status=live}}</ref><ref name=amsterdamarrrest>{{cite news|url=https://apnews.com/article/nicki-minaj-arrest-drugs-amsterdam-tour-f7c05e8ce6ddfc5482dbe37ba368038a|title=Nicki Minaj's England concert postponed after rapper was detained by Dutch authorities over pot|work=[[Associated Press News]]|date=May 25, 2024|access-date=May 25, 2024|archive-url=https://web.archive.org/web/20240607125216/https://apnews.com/article/nicki-minaj-arrest-drugs-amsterdam-tour-f7c05e8ce6ddfc5482dbe37ba368038a|archive-date=June 7, 2024|url-status=live}}</ref>
In January 2025, a lawsuit was filed against Minaj by Brandon Garrett, a former tour employee, alleging assault, battery and "[[intentional infliction of emotional distress]]." He claimed that in April 2024, while he was working as a day-to-day manager on her Pink Friday 2 tour, Minaj struck him multiple times after he had another employee collect her prescription in his place. He alleged that Minaj's former manager, Debra Antney, then informed him that he would not be joining the tour group for their next tour date. Minaj's lawyer, Judd Burstein, denied his allegations, calling them false and frivolous, and expressed confidence that the matter would be resolved in her favor.<ref>{{cite news|last=O'Connor |first=Roisin |title=Nicki Minaj sued for assault by former tour employee |date=January 4, 2025 |url=https://www.independent.co.uk/arts-entertainment/music/news/nicki-minaj-lawsuit-tour-manager-assault-b2673646.html |website=The Independent |access-date=January 5, 2025}}</ref>
==Philanthropy and advocacy==
[[File:Katy Perry and Nicki Minaj (cropped).jpg|thumb|upright|[[Katy Perry]] (left) and Minaj perform for service members at the 2010 VH1 Divas Salute the Troops concert. ]]
Minaj has advocated for victims of natural disasters and [[HIV/AIDS]], service members, women's rights, education, and religious freedom, and donated to various causes and fans. She performed alongside [[Katy Perry]] at the 2010 [[VH1]] Divas Salute the Troops concert,<ref>{{Cite magazine|url=https://www.billboard.com/articles/news/949737/katy-perry-leads-all-star-cast-at-vh1-divas-salute-the-troops|title=Katy Perry Leads All-Star Cast at 'VH1 Divas Salute the Troops'|access-date=August 21, 2020|magazine=Billboard}}</ref><ref>{{Cite web|url=https://consequence.net/2010/12/nicki-minaj-and-katy-perry-cover-girls-just-wanna-have-fun-for-the-troops/|title=Nicki Minaj and Katy Perry Cover 'Girls Just Wanna Have Fun' For The Troops|date=December 6, 2010|access-date=August 21, 2020}}</ref> and had a [[Barbie]] doll created in her likeness to raise funds for [[Project Angel Food]] in 2011, a charity that provides food to victims of HIV/AIDS.<ref>{{Cite magazine|url=https://www.rollingstone.com/music/music-news/nicki-minaj-barbie-doll-made-for-charity-58730/|title=Nicki Minaj Barbie Doll Made for Charity|access-date=September 16, 2020|last=Perpetua|first=Matthew|magazine=[[Rolling Stone]]|date=December 2, 2011}}</ref> In the aftermath of [[Hurricane Sandy in New York|Hurricane Sandy]] in 2012, Minaj donated $15,000 to the [[Food Bank For New York City]] and held a turkey drive at her alma mater, [[Ozone Park, Queens#Schools|PS 45]].<ref>{{cite web|url=http://www.contactmusic.com/news/nicki-minaj-donates-15-000-to-aid-new-york-hunger-charity_3382920|title=Nicki Minaj Donates $15,000 To Aid New York Hunger Charity|website=Contact Music: News|date=November 20, 2012|access-date=November 20, 2013}}</ref> In 2017, she revealed she had been donating money to a village in India for a few years via her pastor, Lydia Sloley, which helped the village open a computer center, a tailoring institute, a reading program, and two water wells.<ref>{{cite magazine |last=Lamarre |first=Carl |date=May 22, 2017 |title=Nicki Minaj Donates Money to Struggling Village in India |url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-donates-money-indian-village-7801304/ |access-date=August 30, 2017 |magazine=Billboard}}</ref> Minaj donated $25,000 to the [[International Red Cross and Red Crescent Movement|Red Cross]] to support [[humanitarian aid]] efforts following [[Hurricane Harvey]] in August 2017.<ref>{{cite web |last=India |first=Lindsey |date=August 29, 2017 |title=Here's What Drake, Nicki Minaj and More Are Doing to Help Houston After Hurricane Harvey Hit |url=http://www.xxlmag.com/news/2017/08/drake-nicki-minaj-houston-hurricane-harvey/ |access-date=August 30, 2017 |website=[[XXL (magazine)|XXL]]}}</ref><ref>{{cite web |last=Howard |first=Jacinta |date=August 29, 2017 |title=Chris Brown, Nicki Minaj, T.I. and DJ Khaled Answer Kevin Hart's Challenge to Donate $25K to Hurricane Harvey Relief |url=http://theboombox.com/chris-brown-nicki-minaj-t-i-and-dj-khaled-answer-kevin-harts-challenge-to-donate-25k-to-hurricane-harvey-relief/ |access-date=August 30, 2017 |website=[[Townsquare Media|The Boombox]]}}</ref> She also paid the college tuition, student loans, and other education expenses of multiple fans on Twitter.<ref>{{cite web|url=https://www.complex.com/music/a/omar-burgess/nicki-minaj-twitter-donations|title=Nicki Minaj Helped Dozens of Fans Pay Bills and Tuition Via Twitter Saturday Night|date=May 7, 2017|access-date=January 1, 2026|work=Complex}}</ref><ref>{{cite magazine|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-relaunch-student-of-the-game-charity-go-to-college-1235819515/|title=Nicki Minaj Is Relaunching Her Student Of The Game Charity & Going To College|date=November 4, 2024|access-date=January 1, 2026|magazine=Billboard}}</ref> She then posted that she would launch an official charity for student loans and tuition relief, which was titled Student of the Game in 2018.<ref>{{cite web|url=https://www.huffpost.com/entry/nicki-minaj-charity-student-loans-tuition_n_591729d0e4b00f308cf592b2|title=Nicki Minaj Is Starting An 'Official Charity' To Pay Off Student Loans|work=[[HuffPost]]|date=May 13, 2017|access-date=January 1, 2026}}</ref><ref>{{cite web|url=https://www.complex.com/music/a/eric-diep/nicki-minaj-fans-who-she-paid-off-student-loans-interview|title=We Spoke to Nicki Minaj Fans Who She Paid Off Student Loans For|work=Complex|date=May 13, 2017|access-date=January 1, 2026}}</ref><ref>{{cite magazine|url=https://www.billboard.com/music/rb-hip-hop/nicki-minaj-awards-37-college-students-scholarship-8463821/|title=Nicki Minaj Awards 37 College Students With Scholarships: See Their Reactions|date=July 2, 2018|access-date=January 1, 2026|magazine=Billboard}}</ref>
In a 2018 episode of ''[[The Ellen DeGeneres Show]]'', Minaj gave out over $150,000 in donations to her fans.<ref>{{cite news |title=Nicki Minaj Gives Away $150,000 to Students on 'Ellen' |url=http://www.xxlmag.com/news/2018/09/nicki-minaj-gives-away-150000-to-students-on-the-ellen-show/ |access-date=September 6, 2018 |work=XXL Mag |date=September 5, 2018}}</ref><ref name="billboard.com" /><ref>{{cite news |title=Nicki Minaj Explains Why She Doesn't Need a Man to Be Happy |url=https://www.etonline.com/nicki-minaj-explains-why-she-doesnt-need-a-man-to-be-happy-108905 |access-date=September 6, 2018 |work=Entertainment Tonight |language=en}}</ref> In 2019, she pulled out of headlining a concert in [[Saudi Arabia]] following online backlash from activists, after women's rights activist [[Loujain al-Hathloul]] was detained and arrested for speaking out against the Saudi regime. Minaj was praised by [[Thor Halvorssen (human rights activist)|Thor Halvorssen]] of [[The Human Rights Foundation]], who criticized her initial plan to perform in a letter. Minaj stated, "While I want nothing more than to bring my show to fans in Saudi Arabia, after better educating myself on the issues, I believe it is important for me to make clear my support for the rights of women, the LGBTQ community and freedom of expression."<ref>{{Cite news|url=https://abcnews.go.com/GMA/News/nicki-minaj-pulls-saudi-arabia-performance-backlash-human/story?id=64228536|title=Nicki Minaj pulls out of Saudi Arabia performance following backlash from human rights activists|work=ABC|access-date=August 21, 2020}}</ref><ref>{{Cite news|url=https://www.theguardian.com/music/2019/jul/09/nicki-minaj-pulls-out-of-saudi-arabia-concert-after-backlash|title=Nicki Minaj pulls out of Saudi Arabia concert after backlash|work=[[The Guardian]]|date=July 9, 2019|access-date=August 21, 2020}}</ref>
In 2020, Minaj donated $25,000 to the St. Jude's Home for Girls school after visiting the school in Trinidad. In a speech, she encouraged the girls to push through hard obstacles, and referenced her own experience with domestic violence.<ref>{{Cite web|date=February 28, 2020|title=Nicki Minaj is donating $25,000 to St. Jude's Home for Girls in Trinidad|url=https://www.dazeddigital.com/music/article/48157/1/nicki-minaj-is-donating-25-000-to-the-st-jude-s-home-for-girls|access-date=September 9, 2021|website=Dazed|language=en}}</ref> Following the release of the song "[[Trollz (song)|Trollz]]" (2020) with [[6ix9ine]], Minaj announced that a portion of its proceeds, along with profits from all merchandise, will go towards the Bail Project amid the [[Black Lives Matter]] protests sparked by the [[murder of George Floyd]].<ref>{{Cite news|url=https://globalnews.ca/news/7058641/tekashi-6ix9ine-nicki-minaj-trollz-bail-project/|title=Tekashi 6ix9ine, Nicki Minaj drop 'Trollz,' proceeds going to Bail Project|access-date=August 27, 2020|work=Global News}}</ref> Minaj was one of over 200 artists who signed an [[open letter]] in April 2024 demanding for [[AI safety|safety from]] the "predatory use of AI" in the music industry.<ref>{{Cite news |last=DeVon |first=Cheyenne |date=April 5, 2024 |title=Billie Eilish, Nicki Minaj, Jon Bon Jovi and over 200 artists call for protections against "predatory use of AI" |url=https://www.cnbc.com/2024/04/05/billie-eilish-nicki-minaj-200-artists-sign-letter-against-ai-music.html |access-date=April 6, 2024 |work=[[CNBC]]}}</ref> In November 2025, Minaj sent a team to Jamaica to deliver relief supplies to people affected by [[Hurricane Melissa]].<ref>{{cite web|url=https://www.iheart.com/content/2025-11-06-nicki-minaj-delivers-supplies-to-victims-of-hurricane-melissa-in-jamaica/|title=Nicki Minaj Delivers Supplies To Victims Of Hurricane Melissa In Jamaica|publisher=[[iHeart]]|date=November 6, 2025|access-date=January 25, 2025}}</ref><ref>{{cite web|url=https://www.hot97.com/news/nicki-minaj-sends-aid-to-jamaica-following-hurricane-melissa/|title=Nicki Minaj Sends Aid To Jamaica Following Hurricane Melissa|publisher=[[Hot97]]|date=November 7, 2025|access-date=January 25, 2025}}</ref> In 2026, Minaj pledged to donate to the [[Trump account|Trump Accounts]], an investment program created to aid the financial future of American children, providing parents a starter fund for their children in a tax-deferred investment account. She said that the program encourages financial literacy and "will benefit everyone, decreasing the gap for future prosperity between children who traditionally aren't born with a full bank account & children who are. I believe it will have the most profound impact on urban communities, which often face greater financial challenges."<ref>{{cite web|title=Nicki Minaj pledges cash to support Trump accounts for ‘Barbz’ babies|url=https://nypost.com/2026/01/28/us-news/nicki-minaj-donates-cash-for-trump-accounts-for-barbz-babies/|work=The New York Post|date=January 28, 2026|access-date=February 16, 2026|archive-url=https://web.archive.org/web/20260129020457/https://nypost.com/2026/01/28/us-news/nicki-minaj-donates-cash-for-trump-accounts-for-barbz-babies/|archive-date=January 29, 2026|url-status=live}}</ref><ref>{{cite web|url=https://abcnews.com/Politics/trump-urge-families-open-trump-accounts-tax-season/story?id=129611160|title=Nicki Minaj helps Trump kick off Trump Accounts for children|publisher=ABC News|date=January 27, 2026|access-date=February 16, 2026|archive-url=https://web.archive.org/web/20260216110137/https://abcnews.com/Politics/trump-urge-families-open-trump-accounts-tax-season/story?id=129611160|archive-date=February 16, 2026|url-status=live}}</ref><ref>{{cite web|url=https://www.c-span.org/program/white-house-event/nicki-minaj-on-trump-investment-accounts-for-children/672504|title=Nicki Minaj on Trump Investment Accounts for Children|date=January 28, 2026|access-date=February 16, 2026|archive-url=https://web.archive.org/web/20260216110725/https://www.c-span.org/program/white-house-event/nicki-minaj-on-trump-investment-accounts-for-children/672504|archive-date=February 16, 2026|url-status=live|publisher=[[C-SPAN]]}}</ref>
===Political views===
In a 2023 interview, Minaj said, "I'm one of those people who doesn't go with a crowd. I like to make my own assessment of everything without help from everyone." She rejected being told who she should campaign for, stating: "there's a lot we don't know that's going on in the government, and I don't think it changes whether you lean to the left or right."<ref>{{cite web|url=https://www.vogue.com/article/nicki-minaj-december-cover-2023-interview|title=The Art of Being Nicki Minaj|work=Vogue|date=November 9, 2023|access-date=February 10, 2026|archive-url=https://web.archive.org/web/20260108073644/https://www.vogue.com/article/nicki-minaj-december-cover-2023-interview|archive-date=January 8, 2026|url-status=live}}</ref>
Minaj has advocated for religious freedom and against political persecution. In 2015, she commented on the [[Charleston church shooting]], in which multiple people were murdered in a church.<ref>{{cite web|url=https://www.nme.com/news/music/various-artists-1078-1225830|title=Taylor Swift, Nicki Minaj, Nas and other music stars respond to Charleston Church shooting|work=[[NME]]|date=June 19, 2015|access-date=February 10, 2026|archive-url=https://web.archive.org/web/20230926094135/https://www.nme.com/news/music/various-artists-1078-1225830|archive-date=September 26, 2023|url-status=live}}</ref> In 2020, she supported [[End SARS]], a series of protests against police brutality in Nigeria, stating: "Standing with & praying for the brave young people of Nigeria who are on the front lines of this senseless violence. Your voice is being heard."<ref>{{Cite tweet |user=NickiMinaj |number=1318699082840371200|title=Standing with & praying for the brave young people of Nigeria who are on the front lines of this senseless violence. Your voice is being heard. 🇳🇬 #EndSARS}}</ref> In November 2025, Minaj denounced [[religious violence in Nigeria|violence]] against [[Christianity in Nigeria|Christians in Nigeria]] in a social media post: "No group should ever be persecuted for practicing their religion ... We don't have to share the same beliefs in order for us to respect each other."<ref>{{cite web|url=https://krcrtv.com/news/nation-world/nikki-minaj-and-un-ambassador-unite-to-address-nigerias-christian-persecution-crisis-trump-administration-donald-trump|title=Nicki Minaj and UN Ambassador unite to address Nigeria's Christian persecution crisis|publisher=[[KRCR-TV]]|date=November 18, 2025|access-date=December 24, 2025}}</ref><ref>{{cite web|url=https://tribune.com.pk/story/2577870/nicki-minaj-to-speak-at-un-on-attacks-against-christians-in-nigeria|title=Nicki Minaj to speak at UN on attacks against Christians in Nigeria|date=November 19, 2025|access-date=December 24, 2025|work=[[The Express Tribune]]}}</ref> She later delivered a speech at the [[United Nations]] advocating for an end to the religious violence, stating: "Sadly, this problem is not only a growing problem in Nigeria, but also in so many other countries around the world ... religious freedom means we all sing our faith regardless of who we are, where we live and what we believe." Her remarks gained media attention, particularly for her support of sitting U.S. President [[Donald Trump]]'s stance on the issue.<ref>{{cite web|url=https://fortune.com/2025/11/19/nicki-minaj-nigeria-christians-trump-united-nations/|title=Nicki Minaj thanks Trump for leadership on Christians in Nigeria, calls for urgent action in UN speech|work=[[Fortune (magazine)|Fortune]]|date=November 19, 2025|access-date=December 24, 2025}}</ref>
In December 2025, Minaj criticized California governor [[Gavin Newsom]]'s statement that he wants "to see [[trans kids]]", commenting: "Imagine being the guy running on wanting to see trans kids.... Not even a trans ADULT would run on that. Normal adults wake up & think they want to see healthy, safe, happy kids."<ref>{{cite web|url=https://www.usatoday.com/story/entertainment/celebrities/2025/12/14/nicki-minaj-slams-gavin-newsom-trans-kids/87763008007/|title=Nicki Minaj slams Gavin Newsom for comments on trans kids|work=USA Today|date=December 14, 2025|access-date=December 24, 2025}}</ref>
[[File:AmericaFest 2025 - Erika Kirk and Nicki Minaj 12.jpg|thumb|Minaj being interviewed by [[Erika Kirk]] at [[Turning Point USA]] 2025]]
She was later interviewed by [[Erika Kirk]] at a [[Turning Point USA]] conference, where she commented on various topics, including her journey as a Christian and the reasons she chose to speak out on these issues. Minaj said that those who are intolerant of people worshiping God should not be in power. On why her decision was seen as controversial, she stated: {{quote|We're not allowed to have a different opinion, we're not allowed to think out loud ... And especially for the young people, I don't want them growing up in a world where they feel like that. They deserve for their voices to be heard, they have valid feelings, and they're thinking about things, and it deserves attention.}}
On why she spoke up, Minaj stated that she "got tired of being pushed around ... then you realize 'I have something inside of me that's stronger than what's out there'", adding, "In a world that doesn't want us to think, we will think. By ourselves, on our own."<ref>{{Cite news |date=December 21, 2025 |title=Nicki Minaj Makes Surprise Appearance at Turning Point USA Event |url=https://www.complex.com/music/a/alex-ocho/nicki-minaj-makes-surprise-appearance-at-turning-point-usa-event |access-date=December 24, 2025 |work=Complex}}</ref><ref>{{cite web |date=December 21, 2025 |title=WATCH: Nicki Minaj discusses faith and politics with Erika Kirk at Turning Point USA summit |url=https://www.pbs.org/newshour/politics/watch-nicki-minaj-discusses-faith-and-politics-with-erika-kirk-at-turning-point-usa-summit |access-date=December 24, 2025 |publisher=[[PBS]]}}</ref> Subsequent to these events, media outlets characterized Minaj's views as having become more [[conservative]], contrasting it with her previous criticism of the anti-immigration policies enacted during the [[first presidency of Donald Trump]].<ref>{{Cite web |date=December 22, 2025 |title=Nicki Minaj praises Trump for 'beating the bad guys' at Turning Point USA appearance |url=https://www.independent.co.uk/bulletin/news/nicki-minaj-trump-praise-turning-point-usa-video-b2888846.html |url-status=live |archive-url=https://web.archive.org/web/20260207195509/https://www.independent.co.uk/bulletin/news/nicki-minaj-trump-praise-turning-point-usa-video-b2888846.html |archive-date=February 7, 2026 |access-date=December 24, 2025 |work=The Independent}}</ref><ref>{{Cite web |last=Paletta |first=Damian |date=December 23, 2025 |title=Nicki Minaj's Surprising Shift Toward Trump and Vance |url=https://www.wsj.com/politics/elections/nicki-minajs-surprising-shift-toward-trump-and-vance-e0cdf1f0 |url-status=live |archive-url=https://web.archive.org/web/20260102101505/https://www.wsj.com/politics/elections/nicki-minajs-surprising-shift-toward-trump-and-vance-e0cdf1f0 |archive-date=January 2, 2026 |access-date=December 24, 2025 |work=The Wall Street Journal}}</ref><ref>{{Cite web |last=Robinson |first=Kinsey Crowley and KiMi |title=Was Nicki Minaj always a Trump supporter? She spoke at Turning Point USA |url=https://www.usatoday.com/story/news/politics/2025/12/22/nicki-minaj-donald-trump-supporter-island-girl/87880591007/ |access-date=December 24, 2025 |website=USA TODAY}}</ref><ref>{{Cite web |author=Mathew Rodriquez |date=December 19, 2025 |title=A Timeline of Nicki Minaj's Descent Into Trump's MAGA World |url=https://www.them.us/story/nicki-minaj-trump-jd-vance-maga-timeline-history |access-date=January 31, 2026 |website=them.us}}</ref><ref>{{Cite web |date=February 1, 2026 |title=How Nicki Minaj went from Queen of Rap to Trump cheerleader |url=https://www.the-independent.com/arts-entertainment/music/news/nicki-minaj-trump-support-reason-politics-b2911630.html |website=The Independent}}</ref>
== Discography ==
{{Main|Nicki Minaj discography}}
{{See also|List of songs recorded by Nicki Minaj}}
<!-- Per [[Wikipedia:WikiProject Musicians/Article guidelines]], The discography section of the musician's primary article should "provide a summary of the musician's major works. In most cases this is done using a simple list of their studio albums, leaving a complete listing of releases to the discography article." -->
'''Studio albums'''
* ''[[Pink Friday]]'' (2010)
* ''[[Pink Friday: Roman Reloaded]]'' (2012)
* ''[[The Pinkprint]]'' (2014)
* ''[[Queen (Nicki Minaj album)|Queen]]'' (2018)
* ''[[Pink Friday 2]]'' (2023)
== Filmography ==
{{Main|Nicki Minaj videography}}
{{hatnote|This section lists select works only. Refer to the main article for further information.}}
<!-- Per [[Wikipedia:WikiProject Musicians/Article guidelines]], The videography section of the person's primary article should "provide a summary of their major works. In most cases this is done using a simple list of their films, leaving a complete listing of releases to the videography article." -->
{{col-begin}}
* ''[[Ice Age: Continental Drift]]'' (2012)
* ''[[The Other Woman (2014 film)|The Other Woman]]'' (2014)
* ''[[Barbershop: The Next Cut]]'' (2016)
* ''[[The Angry Birds Movie 2]]'' (2019)
{{col-end}}
== Tours ==
{{Main|List of Nicki Minaj live performances}}
[[File:Viejas Arena Loading Area - Nicki Minaj Tour, September 27, 2024.jpg|thumb|Minaj's tour buses and trucks parked at [[Viejas Arena]] in San Diego, in September 2024|286x286px]]
'''Headlining'''
* [[Pink Friday Tour]] (2011–2012)
* [[Pink Friday: Reloaded Tour]] (2012)
* [[The Pinkprint Tour]] (2015–2016)
* [[Pink Friday 2 World Tour]] (2024)
'''Co-headlining'''
* [[The Nicki Wrld Tour]] (with [[Juice Wrld]]) (2019)
'''Opening act'''
* [[Lil Wayne]] – [[I Am Music Tour|America's Most Wanted Tour]] (2008)
* Lil Wayne – [[I Am Music II Tour]] (2011)
* [[Britney Spears]] – [[Femme Fatale Tour]] (2011)
== See also ==
* [[East Coast hip hop]]
* [[List of best-selling music artists]]
* [[List of most-followed Instagram accounts]]
* [[List of Trinidadians]]
== References ==
{{Notelist}}
{{Reflist}}
== Further reading ==
* {{cite book|last=Hope|first=Clover|author-link=Clover Hope|title=The Motherlode: 100+ Women Who Made Hip-Hop|year=2021|publisher=Harry N. Abrams|isbn=978-1-4197-4296-5}}
== External links ==
{{Sister project links|v=no|s=no|b=no|display=Nicki Minaj|voy=no}}
* {{Official website}}
* {{AllMusic|class=artist|id=mn0001013175|label=Nicki Minaj}}
* {{Discogs artist|artist=Nicki Minaj}}
* {{IMDb name}}
{{s-start}}
{{succession box|before = [[CeeLo Green|Cee-Lo Green]]|title = ''[[Saturday Night Live]]'' [[list of Saturday Night Live guests|musical guest]]|years = January 29, 2011|after = [[Linkin Park]]}}
{{succession box|before = [[Mark Ronson]] and [[Bruno Mars]]|title = ''Saturday Night Live'' musical guest|years = December 6, 2014|after = [[Charli XCX]]}}
{{s-end}}
{{Nicki Minaj|state=expanded}}
{{Nicki Minaj songs}}
{{Navboxes
|title = [[List of awards and nominations received by Nicki Minaj|Awards for Nicki Minaj]]
|list =
{{American Music Award for Favorite Rap/Hip-Hop Artist}}
{{BET Award for Best Female Hip-Hop Artist}}
{{BET Award for Best New Artist}}
{{BET Award for Viewer's Choice}}
{{Nickelodeon Kids' Choice Award for Favorite Song}}
{{Michael Jackson Video Vanguard Award}}
{{MTV Europe Music Award for Best Hip-Hop}}
{{MTV Europe Music Award for Best Look}}
{{MTV Europe Music Award for Best US Act}}
{{MTV Video Music Award for Best Hip-Hop Video}}
{{People's Choice Award for Favorite Female Artist}}
}}
{{Cash Money Records}}
{{Young Money Entertainment}}
{{Portal bar|Biography|Pop music|New York City|Trinidad and Tobago|}}
{{Authority control}}
{{DEFAULTSORT:Minaj, Nicki}}
[[Category:Nicki Minaj| ]]
[[Category:1982 births]]
[[Category:Living people]]
[[Category:21st-century Trinidad and Tobago women singers]]
[[Category:21st-century women rappers]]
[[Category:Actresses from Queens, New York]]
[[Category:Businesspeople from Queens, New York]]
[[Category:Christians from New York (state)]]
[[Category:Contemporary R&B singers]]
[[Category:East Coast hip-hop musicians]]
[[Category:Feminist musicians]]
[[Category:Fiorello H. LaGuardia High School alumni]]
[[Category:Hardcore hip-hop artists]]
[[Category:Judges in American reality television series]]
[[Category:MTV Europe Music Award winners]]
[[Category:MTV Video Music Award winners]]
[[Category:Musicians from Port of Spain]]
[[Category:Musicians with fictional stage personas]]
[[Category:People from Jamaica, Queens]]
[[Category:Rappers from Queens, New York]]
[[Category:Republic Records artists]]
[[Category:Singer-songwriters from New York (state)]]
[[Category:Trinidad and Tobago Christians]]
[[Category:Trinidad and Tobago emigrants to the United States]]
[[Category:Trinidad and Tobago feminists]]
[[Category:Trinidad and Tobago people of Dougla descent]]
[[Category:Trinidad and Tobago singer-songwriters]]
[[Category:Universal Motown Records artists]]
[[Category:Women pop singers]]
[[Category:Women hip-hop singers]]
[[Category:Young Money Entertainment artists]]
gvj410gotys6n33kdvbu1uu6mb1lyn7
QuickCategories CI Test/1
0
103055
769916
768743
2026-10-24T10:10:33Z
Lucas Werkmeister CI
44821
setup
769916
wikitext
text/x-wiki
Test page for the QuickCategories tool.
[[Category:Already present cat]]
[[Category:Removed cat]]
Bottom text
fqdbeff2h5awq7c2zzive0olzeyhsfw
769921
769916
2026-10-24T10:10:36Z
Lucas Werkmeister CI
44821
+[[Category:Added cat]], (+[[Category:Already present cat]]), -[[Category:Removed cat]], (-[[Category:Not present cat]]); QuickCategories CI test
769921
wikitext
text/x-wiki
Test page for the QuickCategories tool.
[[Category:Already present cat]]
[[Category:Added cat]]
Bottom text
204gsj1o0z237pck8veepxok5gwhypa
769924
769921
2026-10-24T10:10:37Z
Lucas Werkmeister CI
44821
teardown
769924
wikitext
text/x-wiki
Test page for the QuickCategories tool.
grb81tg5g5vswnms329tmfr3s8kxvgf
QuickCategories CI Test Redirect Target/1
0
107379
769918
768745
2026-10-24T10:10:34Z
Lucas Werkmeister CI
44821
setup
769918
wikitext
text/x-wiki
Test page for the QuickCategories tool.
[[Category:Already present cat]]
[[Category:Removed cat]]
Bottom text
fqdbeff2h5awq7c2zzive0olzeyhsfw
769922
769918
2026-10-24T10:10:36Z
Lucas Werkmeister CI
44821
+[[Category:Added cat]], (+[[Category:Already present cat]]), -[[Category:Removed cat]], (-[[Category:Not present cat]]); QuickCategories CI test
769922
wikitext
text/x-wiki
Test page for the QuickCategories tool.
[[Category:Already present cat]]
[[Category:Added cat]]
Bottom text
204gsj1o0z237pck8veepxok5gwhypa
769926
769922
2026-10-24T10:10:38Z
Lucas Werkmeister CI
44821
teardown
769926
wikitext
text/x-wiki
Test page for the QuickCategories tool.
grb81tg5g5vswnms329tmfr3s8kxvgf
QuickCategories CI Test Redirect/1
0
107382
769917
768744
2026-10-24T10:10:34Z
Lucas Werkmeister CI
44821
setup
769917
wikitext
text/x-wiki
#REDIRECT [[QuickCategories CI Test Redirect Target/1]]
[[Category:Unchanged cat]]
47qf8s6ngll4u0wdnabdw3j31blmg56
769925
769917
2026-10-24T10:10:38Z
Lucas Werkmeister CI
44821
teardown
769925
wikitext
text/x-wiki
#REDIRECT [[QuickCategories CI Test Redirect Target/1]]
cpyhreevz6hd676k46165q4smew6317
QuickCategories CI Test Other Redirect Target/1
0
107385
769920
768747
2026-10-24T10:10:35Z
Lucas Werkmeister CI
44821
setup
769920
wikitext
text/x-wiki
Test page for the QuickCategories tool.
[[Category:Unchanged cat]]
Bottom text
czt1xsn8epqffiq0nviakd7rv65clnp
769928
769920
2026-10-24T10:10:39Z
Lucas Werkmeister CI
44821
teardown
769928
wikitext
text/x-wiki
Test page for the QuickCategories tool.
grb81tg5g5vswnms329tmfr3s8kxvgf
QuickCategories CI Test Other Redirect/1
0
107388
769919
768746
2026-10-24T10:10:35Z
Lucas Werkmeister CI
44821
setup
769919
wikitext
text/x-wiki
#REDIRECT [[QuickCategories CI Test Other Redirect Target/1]]
[[Category:Already present cat]]
[[Category:Removed cat]]
3eziw6y0xf3xdrqdgw85iskjsh4iby9
769923
769919
2026-10-24T10:10:36Z
Lucas Werkmeister CI
44821
+[[Category:Added cat]], (+[[Category:Already present cat]]), -[[Category:Removed cat]], (-[[Category:Not present cat]]); QuickCategories CI test
769923
wikitext
text/x-wiki
#REDIRECT [[QuickCategories CI Test Other Redirect Target/1]]
[[Category:Already present cat]]
[[Category:Added cat]]
a0fxjxdl5eleb4iqtwr70rkxo5vsdu6
769927
769923
2026-10-24T10:10:38Z
Lucas Werkmeister CI
44821
teardown
769927
wikitext
text/x-wiki
#REDIRECT [[QuickCategories CI Test Other Redirect Target/1]]
m71v87kxldkmcdy4ksdtl73icoftpz6
Oil shale industry
0
113506
769850
689896
2026-10-23T19:54:52Z
InternetArchiveBot
34092
Rescuing 1 sources and tagging 0 as dead.) #IABot (v2.0.9.6
769850
wikitext
text/x-wiki
{{About|industrial use of a sedimentary rock known as [[oil shale]]|production of crude oil trapped in oil-bearing shales (tight oil)|Tight oil}}
[[File:VKG Energia panoraam.jpg|thumb|[[Viru Keemia Grupp|VKG]] Energia in [[Estonia]].]]
The '''oil shale industry''' is an industry of [[mining]] and processing of [[oil shale]]—a fine-grained [[sedimentary rock]], containing significant amounts of [[kerogen]] (a solid mixture of organic [[chemical compound]]s), from which liquid [[hydrocarbon]]s can be manufactured. The industry has developed in [[Brazil]], China, [[Estonia]] and to some extent in Germany and Russia. Several other countries are currently conducting research on their [[oil shale reserves]] and [[Shale oil extraction|production methods]] to improve efficiency and recovery.<ref name=wec>
{{cite book
| title = Survey of energy resources
| chapter = Oil Shale
| last = Dyni | first = John R.
| editor1-last= Clarke |editor1-first= Alan W.
| editor2-last= Trinnaman |editor2-first= Judy A.
| publisher = [[World Energy Council|WEC]]
| year = 2010
| edition = 22
| pages = 93–123
| url = http://www.worldenergy.org/documents/ser_2010_report.pdf
| format = PDF
| isbn = 978-0-946121-02-1
| archive-url = https://web.archive.org/web/20131109102909/http://www.worldenergy.org/documents/ser_2010_report.pdf
| archive-date = 2013-11-09
| url-status = dead}}
</ref> Estonia accounted for about 70% of the world's oil shale production in a study published in 2005.<ref name=research>
{{Cite journal
|title = Non-Nuclear Energy Research in Europe – A comparative study. Country Reports A – I. Volume 2
|publisher = [[European Commission]]. Directorate-General for Research
|year = 2005
|url = http://ec.europa.eu/research/energy/pdf/synergy_vol2_en.pdf
|format = PDF
|id = EUR 21614/2
|accessdate = 2007-06-29
|url-status = dead
|archiveurl = https://web.archive.org/web/20071025105404/http://ec.europa.eu/research/energy/pdf/synergy_vol2_en.pdf
|archivedate = 2007-10-25
}}
</ref>
Oil shale has been used for industrial purposes since the early 17th century, when it was mined for its minerals. Since the late 19th century, [[shale oil]] has also been used for its oil content and as a low grade fuel for power generation. However, barring countries having significant oil shale deposits, its use for power generation is not particularly widespread. Similarly, oil shale is a source for production of [[synthetic crude]] oil and it is seen as a solution towards increasing domestic production of oil in countries that are reliant on imports.
==History==
{{main|History of the oil shale industry}}
[[File:Production of oil shale.png|thumb|350px|right|Production of oil shale in millions of metric tons, from 1880 to 2010. Source: Pierre Allix, Alan K. Burnham.<ref name=oilfield>{{Cite journal
| last1 = Allix
| first1 = Pierre
| last2 = Burnham
| first2 = Alan K.
| title = Coaxing Oil from Shale
| journal = Oilfield Review
| publisher = [[Schlumberger]]
| date = 2010-12-01
| issue = 4
| volume = 22
| page = 6
| url = http://www.slb.com/~/media/Files/resources/oilfield_review/ors10/win10/coaxing.ashx
| format = PDF
| accessdate = 2012-04-18
| archive-url = https://web.archive.org/web/20150106093639/http://www.slb.com/~/media/Files/resources/oilfield_review/ors10/win10/coaxing.ashx
| archive-date = 2015-01-06
| url-status = dead
}}</ref>]]
Oil shale has been used since ancient times. Modern industrial oil shale mining began in 1837 at the Autun mines in France, followed by Britain, Germany and several other countries.<ref name=wec/><ref name=laherrere>{{Cite journal
|last = Laherrère
|first = Jean
|title = Review on oil shale data
|publisher = Hubbert Peak
|year = 2005
|url = http://www.hubbertpeak.com/laherrere/OilShaleReview200509.pdf
|format = PDF
|accessdate = 2007-06-17
|archive-date = 2007-09-28
|archive-url = https://web.archive.org/web/20070928004607/http://www.hubbertpeak.com/laherrere/OilShaleReview200509.pdf
|url-status = dead
}}</ref> The oil shale industry started growing just before [[World War I]] because of the mass production of automobiles and trucks and the supposed shortage of gasoline for transportation needs. In 1924, the [[Tallinn]] Power Plant was the first power plant in the world to switch to oil shale firing.<ref name=arvoots>
{{Cite journal
| last =Ots | first =Arvo
| title =Estonian oil shale properties and utilization in power plants
| journal = Energetika
| publisher = Lithuanian Academy of Sciences Publishers
| volume = 53
| issue = 2
| pages = 8–18
| date = 2007-02-12
| url= http://elibrary.lt/resursai/LMA/Energetika/2007_2/4.pdf
| format = PDF
| accessdate = 2016-10-29
| archive-url = https://web.archive.org/web/20161029172128/http://elibrary.lt/resursai/LMA/Energetika/2007_2/4.pdf
| archive-date = 2016-10-29
| url-status = live}}
</ref>
Following the end of [[World War II]], the oil shale industry declined due to the discovery of large supplies of easily accessible and cheaper crude oil.<ref name=wec/><ref name=laherrere/><ref name=dyni>
{{Cite book
| last =Dyni | first =John R.
| title =Geology and resources of some world oil-shale deposits. Scientific Investigations Report 2005–5294
| publisher = [[United States Geological Survey]]
| year =2006
| url = http://pubs.usgs.gov/sir/2005/5294/pdf/sir5294_508.pdf
| format=PDF
| accessdate = 2011-11-15}}
</ref><ref name=fushun2>{{Cite conference
| last = Yin
| first = Liang
| title = Current status of oil shale industry in Fushun, China
| publisher = International Oil Shale Conference
| date = 2006-11-07
| place = Amman, Jordan
| url = http://www.sdnp.jo/International_Oil_Conference/rtos-A106.pdf
| format = PDF
| accessdate = 2007-06-29
| conference =
| archive-url = https://web.archive.org/web/20070928110915/http://www.sdnp.jo/International_Oil_Conference/rtos-A106.pdf
| archive-date = 2007-09-28
| url-status = dead
}}</ref> Oil shale production however, continued to grow in Estonia, Russia and China.
Following the [[1973 oil crisis]], the oil shale industry was restarted in several countries, but in the 1980s, when [[1980s oil glut|oil prices fell]], many industries faced closure. The global oil shale industry has grown again from the mid-1990s. In 2003, the oil shale development program was initiated in the United States, and in 2005, the commercial leasing program for oil shale and tar sands was introduced.<ref name=blm>
{{cite pressrelease
|publisher = Bureau of Land Management
|url = http://www.blm.gov/wo/st/en/info/newsroom/2005/september/NR_050920.html
|title = Nominations for Oil Shale Research Leases Demonstrate Significant Interest in Advancing Energy Technology. Press release
|date = 2005-09-20
|accessdate = 2007-07-10
|url-status = dead
|archiveurl = https://web.archive.org/web/20080916051633/http://www.blm.gov/wo/st/en/info/newsroom/2005/september/NR_050920.html
|archivedate = 2008-09-16
}}
</ref><ref name=anl>{{cite web
| publisher= Oil Shale and Tar Sands Leasing Programmatic EIS Information Center
| url= http://ostseis.anl.gov/eis/what/index.cfm
| title= What's in the Oil Shale and Tar Sands Leasing Programmatic EIS
| accessdate= 2007-07-10
| archive-url= https://web.archive.org/web/20070703105351/http://ostseis.anl.gov/eis/what/index.cfm
| archive-date= 2007-07-03
| url-status= dead
}}</ref>
As of May 2007, Estonia is actively engaged in exploitation of oil shale on a significant scale and accounts for 70% of the world's processed oil shale.<ref name=bbj>
{{cite web
| publisher= BBJ
| url= http://www.bbj.hu/index.php?id=37240
| title = Tiny Estonia could go nuclear, sees oil shale hope
| date=2008-03-06
| accessdate=2010-10-09
| archive-url = https://web.archive.org/web/20150323015136/http://www.bbj.hu/index.php?id=37240
| archive-date =2015-03-23
| url-status = dead}}
</ref> Estonia is unique in that its oil shale deposit account for just 17% of total deposits in [[European Union]] but it generates 90% of its power from oil shale. Oil shale industry in Estonia employs 7,500 people, which is about 1% of national employment, accounting for 4% of its gross domestic product.<ref name="eu"/>
==Mining==
Oil shale is mined either by traditional [[underground mining (soft rock)|underground mining]] or [[surface mining]] techniques. There are several mining methods available, but the common aim of all these methods is to fragment the oil shale deposits in order to enable the transport of shale fragments to a power plant or retorting facility. The main methods of [[surface mining]] are ''[[open pit mining]]'' and ''[[strip mining]]''. An important method of [[Underground mining (hard rock)|sub-surface mining]] is the ''[[Room and pillar|room-and-pillar method]]''.<ref name=rand>
{{Cite journal
| last = Bartis | first = James T.
| last2 =LaTourrette | first2 = Tom
| last3 = Dixon | first3 =Lloyd
| last4 = Peterson | first4 =D.J.
| last5 = Cecchine | first5 = Gary
| title = Oil Shale Development in the United States. Prospects and Policy Issues. Prepared for the National Energy Technology Laboratory of the U.S. Department of Energy
| publisher = [[RAND|The RAND Corporation]]
| year = 2005
| url = http://www.rand.org/pubs/monographs/2005/RAND_MG414.pdf
| format=PDF
| isbn = 978-0-8330-3848-7
| accessdate =2007-06-29}}
</ref> In this method, the material is extracted across a horizontal plane while leaving "pillars" of untouched material to support the roof. These pillars reduce the likelihood of a collapse. Oil shale can also be obtained as a by-product of [[coal mining]].<ref name=wec/>
The largest oil shale mine in the world is the Estonia Mine, operated by [[Enefit Kaevandused]].<ref name=ep>
{{Cite press release
| title = Minister of Social Affairs Jaak Aab acquainted himself with the working conditions of the miners
| publisher = [[Eesti Põlevkivi]]
| date = 2006-01-25
| url = http://www.ep.ee/?id=20508
| accessdate =2007-07-29
| archiveurl=https://web.archive.org/web/20070814033514/http://www.ep.ee/?id=20508
| archivedate = 2007-08-14
| url-status = dead}}
</ref> In 2005, Estonia mined 14.8 million tonnes of oil shale.<ref name="eu"/> During the same period, mining permits were issued for almost 24 million tonnes, with applications being received for mining an additional 26 million tonnes.<ref name=nationalplan>
{{Cite web
|title = Mining and use of oil shale to be based on interests of state
|publisher = Ministry of the Environment of Estonia
|year = 2008
|url = http://www.envir.ee/961665
|accessdate = 2008-03-06
|archiveurl = https://web.archive.org/web/20080229103340/http://www.envir.ee/961665
|archivedate = 2008-02-29
|url-status = dead
}}
</ref> In 2008, the [[Riigikogu|Estonian Parliament]] approved the "National Development Plan for the Use of Oil Shale 2008-2015", which limits the annual extraction of oil shale to 20 million tonnes.<ref>
{{Cite web
|title = Riigikogu approves development plan for use of oil shale
|publisher = Ministry of the Environment of Estonia
|year = 2008
|url = http://www.envir.ee/1082834
|accessdate = 2008-11-02
|archiveurl = https://web.archive.org/web/20081221231255/http://www.envir.ee/1082834
|archivedate = 2008-12-21
|url-status = dead
}}
</ref>
==Power generation==
[[File:Эстонская ЭС.jpg|thumb|Oil-shale-fired [[Eesti Power Plant]] in [[Narva]], Estonia.]]
Oil shale can be used as a fuel in thermal power plants, wherein oil shale is burnt like [[coal]] to drive the steam turbines. As of 2012, there are oil shale-fired power plants in Estonia with a generating capacity of 2,967 [[megawatts]] (MW), China, and Germany.<ref name=symposium>
{{Cite journal
| last = Brendow | first = K.
| title = Global oil shale issues and perspectives. Synthesis of the Symposium on Oil Shale. 18–19 November, Tallinn
| journal =Oil Shale. A Scientific-Technical Journal
| publisher = Estonian Academy Publishers
| volume =20
| issue =1
| pages =81–92
| year = 2003
| url=http://www.kirj.ee/public/oilshale/9_brendow_1_03.pdf
| format = PDF
| issn = 0208-189X
| accessdate =2007-07-21}}
</ref><ref name=qian3>
{{Cite conference
| last1 =Qian | first1 =Jialin
| last2 =Wang | first2 =Jianqiu
| last3 =Li | first3 =Shuyuan
| title = One Year's Progress in the Chinese Oil Shale Business
| publisher = [[China University of Petroleum]]
| conference = 27th Oil Shale Symposium
| location = [[Golden, Colorado]]
| url= http://www.ceri-mines.org/documents/27symposium/presentations/av02-1qian.pdf
| format = PDF
| date = 2007-10-15
| accessdate =2011-05-08}}
</ref>
Also [[Israel]], [[Romania]] and Russia have run oil shale-fired power plants, but have shut them down or switched to other fuels like natural gas.<ref name=wec/><ref name=symposium/><ref name=globes220311>
{{Cite news
| last1 = Azulai | first1 =Yuval
| title = We are not drying up the Dead Sea
| newspaper= [[Globes]]
| date = 2011-03-22
| url = http://www.globes.co.il/en/article-1000632377
| accessdate =2012-03-15
| archive-url = https://web.archive.org/web/20160304193604/http://www.globes.co.il/en/article-1000632377
| archive-date = 2016-03-04
| url-status = live}}
</ref> [[Jordan]] and [[Egypt]] have announced their plans to construct oil shale-fired power plants, while Canada and [[Turkey]] plan to burn oil shale at the power plants along with coal.<ref name=wec/><ref name=symposium/><ref name=alali/>
Thermal power plants which use oil shale as a fuel mostly employ two types of combustion methods. The traditional method is ''[[Pulverised fuel firing|Pulverized combustion]]'' (PC) which is used in the older units of oil shale-fired power plants in Estonia, while the more advanced method is ''[[Fluidized bed combustion]]'' (FBC), which is used in the [[Holcim]] cement factory in Dotternhausen, Germany, and was used in the [[Mishor Rotem power plant]] in Israel. The main FBC technologies are ''Bubbling fluidized bed combustion'' (BFBC) and ''Circulating fluidized bed combustion'' (CFBC).<ref name=symposium/><ref name=jordan2>{{Cite journal
| last = Alali
| first = Jamal
| last2 = Abu Salah
| first2 = Abdelfattah
| last3 = Yasin
| first3 = Suha M.
| last4 = Al Omari
| first4 = Wasfi
| title = Oil Shale in Jordan
| publisher = Natural Resources Authority of Jordan
| year = 2015
| url = http://www.memr.gov.jo/EchoBusV3.0/SystemAssets/PDFs/AR/MineralTR/Oil%20Shale.pdf
| format = PDF
| accessdate = 2016-10-29
| archive-date = 2018-11-13
| archive-url = https://web.archive.org/web/20181113065148/http://www.memr.gov.jo/EchoBusV3.0/SystemAssets/PDFs/AR/MineralTR/Oil%20Shale.pdf
| url-status = dead
}}</ref>
There are more than 60 power plants around the world, which are using CFBC technology for combustion of coal and [[lignite]], but only two new units at [[Narva Power Plants]] in Estonia, and one at [[Huadian City|Huadian]] Power Plant in China use CFBC technology for combustion of oil shale.<ref name=qian3/><ref name=alali/><ref name=Estonia>
{{Cite journal
| last = Liive | first = Sandor
| title = Oil Shale Energetics in Estonia
| journal =Oil Shale. A Scientific-Technical Journal
| publisher = Estonian Academy Publishers
| volume = 24
| issue =1
| pages = 1–4
| year = 2007
| url = http://www.kirj.ee/public/oilshale/oil-2007-1-1.pdf
| format = PDF
| issn = 0208-189X
| accessdate = 2007-10-23}}
</ref><ref name=oilshale1>{{Cite journal
| last =Liblik | first =V.
| last2 =Kaasik | first2 =M.
| last3 =Pensa | first3 =M.
| last4 = Rätsep | first4 = A.
| last5 =Rull | first5 =E.
| last6 =Tordik | first6 =A.
| title =Reduction of sulphur dioxide emissions and transboundary effects of oil shale based energy production
| journal =Oil Shale. A Scientific-Technical Journal
| publisher = Estonian Academy Publishers
| volume =23
| issue =1
| pages =29–38
| year =2006
| url=http://www.kirj.ee/public/oilshale/oil-2006-1-3.pdf
| format = PDF
| issn = 0208-189X
| accessdate =2007-06-16}}
</ref> The most advanced and efficient oil shale combustion technology is ''Pressurized fluidized-bed combustion'' (PFBC). However, this technology is still premature and is in its nascent stage.<ref name=agabus>
{{Cite journal
| last =Agabus | first =H.
| last2 =Landsberg | first2 =M.
| last3 =Tammoja | first3 =H.
| title =Reduction of CO<sub>2</sub> emissions in Estonia during 2000–2030
| journal =Oil Shale. A Scientific-Technical Journal
| publisher = Estonian Academy Publishers
| volume =24
| issue = 2 Special
| pages =209–224
| year =2007
| url=http://www.kirj.ee/public/oilshale/oil-2007-2s-3.pdf
| format = PDF
| issn = 0208-189X
| accessdate =2007-09-02}}
</ref>
==Oil extraction==
{{update section|date=April 2020}}
{{Main|Shale oil extraction}}
[[File:Oil shale extraction overview.png|thumb|right|alt=A vertical [[flowchart]] begins with an [[oil shale]] deposit and follows two major branches. Conventional ''ex situ'' processes, shown on the right, proceed through mining, crushing, and retorting. [[Spent shale]] output is noted. ''In situ'' process flows are shown in the left branch of the flowchart. The deposit may or may not be fractured; in either case, the deposit is retorted and the oil is recovered. The two major branches converge at the bottom of the chart, indicating that extraction is followed by [[refining]], which involves thermal and chemical treatment and [[hydrogenation]], yielding liquid fuels and useful byproducts.|Overview of shale oil extraction]]
The major shale oil producers are China and Estonia, with Brazil a distant third, while Australia, USA, Canada and Jordan have planned to set up or restart shale oil production.<ref name=emd>{{Cite web
| last = Burnham
| first = Alan K.
| title = 2016 EMD Oil Shale Committee Final Report
| publisher = [[American Association of Petroleum Geologists]]
| date = 2016-05-18
| url = http://www.i2massociates.com/downloads/F-2016-EMDOilShaleCommitteeAnnualReport.pdf
| format = PDF
| pages = 3–10
| accessdate = 2017-01-16
| archive-url = https://web.archive.org/web/20200429163404/http://www.i2massociates.com/downloads/F-2016-EMDOilShaleCommitteeAnnualReport.pdf
| archive-date = 2020-04-29
| url-status = dead}}</ref><ref name=symposium/><ref name=alali/> According to the [[World Energy Council]], in 2008 the total production of shale oil from oil shale was 930,000 tonnes, equal to {{convert|17700|oilbbl/d}}, of which China produced 375,000 tonnes, Estonia 355,000 tonnes, and Brazil 200 tonnes. In comparison, production of the conventional oil and natural gas liquids in 2008 amounted 3.95 billion tonnes or {{convert|82.12|Moilbbl/d}}.<ref name=wec/>
Although there are several oil shale retorting technologies, only four technologies are currently in commercial use. These are [[Kiviter process|Kiviter]], [[Galoter process|Galoter]], [[Fushun process|Fushun]], and [[Petrosix]].<ref name=qian>{{Cite conference
|last = Qian
|first = Jialin
|author2 = Wang Jianqiu
|title = World oil shale retorting technologies
|publisher = Jordanian Natural Resources Authority
|conference = International Oil Shale Conference
|date = 2006-11-07
|location = [[Amman]], [[Jordan]]
|url = http://www.sdnp.jo/International_Oil_Conference/rtos-A118.pdf
|format = PDF
|accessdate = 2007-06-29
|archive-url = https://web.archive.org/web/20080527234146/http://www.sdnp.jo/International_Oil_Conference/rtos-A118.pdf
|archive-date = 2008-05-27
|url-status = dead
}}</ref> The two main methods of extracting oil from shale are ''ex-situ'' and ''in-situ''. In ''ex-situ'' method, the oil shale is mined and transported to the retort facility in order to extract the oil. The ''in-situ'' method converts the kerogen while it is still in the form of an oil shale deposit, and then extracts it via a well, where it rises up as normal petroleum.<ref name=AICHE>{{Cite conference
| last = Burnham
| first = Alan K.
| last2 = McConaghy
| first2 = James R.
| title = Comparison of the acceptability of various oil shale processes
| date = 2006-10-16
| location = [[Golden, Colorado]]
| conference = 26th Oil shale symposium
| publisher = [[Lawrence Livermore National Laboratory]]
| url = https://e-reports-ext.llnl.gov/pdf/341283.pdf
| format = PDF
| id = UCRL-CONF-226717
| accessdate = 2007-05-27
| archive-url = https://web.archive.org/web/20160213164539/https://e-reports-ext.llnl.gov/pdf/341283.pdf
| archive-date = 2016-02-13
| url-status = dead
}}</ref>
{| class="wikitable" style="margin:left"
|+''Shale oil production projects (operational or in the development phase)''
!Company
!Location
!Method
!Status
!Annual Production
|----
|Aqaba Petroleum<ref name=jordan2/><ref name=emd/>
|Wadi Naddeya, Jordan
|Hot recycled solids ([[Galoter process]])
|Memorandum of understanding
|
|----
|Al Qamar for Energy and Infra Structure<ref name=jordan2/><
|Attarat Umm Ghudran, Jordan
|Hot recycled solids ([[Galoter process]])
|Memorandum of understanding
|
|----
|Beipiao Coal Mining Company
|[[Beipiao]], [[Liaoning]], China
|Internal combustion ([[Fushun process]])
|Operational<ref name=li/>
|
|----
|Daqing Oil Company<br/>(parent: [[CNPC]])
|[[Mudanjiang]], [[Heilongjiang]], China
|Hot recycled solids
|Under construction<ref name=li/>
|
|-
|Dragon Shale
|Utah, USA
|Cross-flow pyrolysis
|Demonstration plant<ref>{{Cite web|url=https://www.dragonshale.com/2018/10/02/dragon-shale-cross-flow-retort-produces-its-first-kerogen-oil/|title=cross flow retort produces first kerogen oil {{!}} Dragon Shale LLC|website=www.dragonshale.com|language=en-US|access-date=2018-10-15|archive-date=2018-10-15|archive-url=https://web.archive.org/web/20181015153241/https://www.dragonshale.com/2018/10/02/dragon-shale-cross-flow-retort-produces-its-first-kerogen-oil/|url-status=dead}}</ref>
|
|----
|[[Eesti Energia]] (Enefit)<ref name=emd/>
|[[Narva]], Estonia
|Hot recycled solids ([[Galoter process]])<br/>Hot recycled solids ([[Galoter process|Enefit process]])<ref name=emd/>
|Operational<br/>Construction
| 1.2 million barrels<br/>(2011)<ref name=ee>
{{Cite journal
| title = Eesti Energia Annual Report 2010/2011
| publisher = Eesti Energia
| year = 2011
| url = https://www.energia.ee/-/doc/10187/pdf/concern/annual_report_2011_eng.pdf
| format= PDF
| accessdate =2012-05-16}}
</ref>
|----
|[[Enefit American Oil]]<br/>(parent: [[Eesti Energia]])
|Utah, USA
|Hot recycled solids ([[Galoter process|Enefit process]])
|Testing
|
|----
|[[Fushun Mining Group]]<ref name=emd/>
|[[Fushun]], China
|Internal combustion ([[Fushun process]])<br/>Hot solids recycle ([[Alberta Taciuk Process]])<ref name=emd/>
|Operational
|2.2 million barrels<br/>(2011)<ref name=li/>
|----
|Gansu Coal Company<ref name=emd/>
|[[Gansu]], China
|Internal combustion ([[Gas combustion retort process|Gas combustion retort]])
|Operational<ref name=li/>
|
|----
|Global Oil Shale Holdings<ref name=jordan2/><ref name=emd/>
| Attarat Umm Al Ghudran and Isfir Al Mahattah, [[Jordan]]
|Externally generated hot gas (PRIX (modified Petrosix) process)
|Memorandum of understanding
|
|----
|Hong Sheng Energy Company
|[[Huadian, Jilin]], China
|Internal combustion ([[Fushun process|Modified Fushun process]])
|Operational<ref name=li/>
|
|----
|[[Independent Energy Partners]]<ref name=emd/>
|Colorado, USA
|True ''in-situ'' process (fuel cell process)
|Testing
|
|----
|[[Inter RAO]]
|Al Attarat, Jordan
|Hot recycled solids ([[Galoter process]])
|Memorandum of understanding
|
|----
|Irati Energy Limited<ref name=emd/>
|Brazil
|Externally generated hot gas ([[Petrosix]] process, PRIX (modified Petrosix) process)
|Planned
|
|----
|Jordan Oil Shale Company<br/>(parent: [[Royal Dutch Shell]])<ref name=jordan2/><ref name=emd/>
|Azraq and Al-Jafr blocks, Jordan
|True ''in-situ'' process ([[Shell in situ conversion process|ICP]])
|Testing
|
|----
|Jordan Oil Shale Energy<br/>(parent: [[Eesti Energia]])<ref name=jordan2/><ref name=emd/>
|[[Attarat Umm Ghudran]], Jordan
|Hot recycled solids ([[Galoter process|Enefit process]])
|Testing
|
|----
|Jordan Oil Shale Energy Company
|Sultani, Jordan
|Hot recycled solids ([[Galoter process]])
|Memorandum of understanding
|
|----
|Julin Energy & Communication Corp.<ref name=emd/>
|China
|Internal combustion ([[Fushun process|Modified Fushun process]])
|
|
|----
|Karak International Oil<br/>(parent: Jordan Energy and Mining)<ref name=jordan2/><ref name=emd/>
|El Lajjun, Jordan
|Hot recycled solids ([[Alberta Taciuk process]])
|Concession agreement signed
|
|----
|Kiviõli Keemiatööstus<ref name=emd/>
|[[Kiviõli]], Estonia
|Internal combustion ([[Kiviter process]])<br/>Hot recycled solids ([[Galoter process]])
|Operational
|0.3 million barrels
|----
|[[Liaoning Chengda]]
|[[Jimsar County]], [[Xinjiang]], China
|Internal combustion ([[Fushun process|Modified Fushun process]])
|Under construction<ref name=li/>
|
|----
|Longhua Harbin Coal Chemical Industry Company<ref name=wang>{{Cite journal| last1 = Wang | first = W. D.| last2 = Zhou | first2 = C.Y.| title = Retorting of pulverized oil shale in fluidized-bed pilot plant| journal = [[Oil Shale (journal)|Oil Shale. A Scientific-Technical Journal]]| publisher = Estonian Academy Publishers| volume = 26| issue = 2| pages = 108–113 | year = 2009| url = http://www.kirj.ee/public/oilshale_pdf/2009/issue_2/oil-2009-2-108-113.pdf | format = PDF| issn = 0208-189X| accessdate = 2016-10-29| doi = 10.3176/oil.2009.2.03}}
</ref><br/>(parent: [[China National Coal Corporation]])<ref name=li/><ref name=wang/>
|[[Yilan County, Heilongjiang|Yilan]], [[Heilongjiang]], China<ref name=li/><ref name=wang/>
|Hot recycled solids (two-vessel fluidized-bed retorting)
|Demonstration project<ref name=li/>
|
|----
|Longkou Mining Group<br/>(parent: [[Shandong Energy]])<ref name=emd/>
|[[Longkou]], China
|Internal combustion ([[Fushun process]])
|Operational
|0.7 million barrels (2011)<ref name=li>
{{Cite journal
| last =Li | first =Shuyuan
| title =The developments of Chinese oil shale activities
| journal = [[Oil Shale (journal)|Oil Shale. A Scientific-Technical Journal]]
| publisher = Estonian Academy Publishers
| volume =29
| issue = 2
| pages =101–102
| year =2012
| url=http://www.kirj.ee/public/oilshale_pdf/2012/issue_2/Oil-2012-2-101-102.pdf
| format = PDF
| issn = 0208-189X
| accessdate =2012-05-31
| doi=10.3176/oil.2012.2.01}}
</ref>
|----
|Longteng Energy Development Co.<ref name=emd/>
|[[Wangqing]], [[Jilin]], China
|Internal combustion ([[Fushun process]])
|Operational<ref name=li/>
|
|----
|[[Maoming Petrochemical Co.]]<br/>(parent: [[Sinopec]])<ref name=emd/>
|China
|Internal combustion ([[Fushun process|Modified Fushun process]])
|Operational
|
|----
|[[Mountain West Energy]]
|Utah, USA
|True ''in-situ'' process (IGE process)
|Testing
|
|----
|Northwood Exploration Israel
|[[Mishor Rotem]], [[Israel]]
|Hot recycled solids ([[Galoter process]])
|Feasibility study
|----
|[[Petrobras]]
|[[São Mateus do Sul]], [[Paraná (state)|Paraná]], [[Brazil]]
|Externally generated hot gas ([[Petrosix]] process)
|Operational
|2.2 million barrels<br/>(2010)
|----
|[[Petrobras]]<br/>[[Total S.A.]]
|Attarat Umm Ghudran, Jordan
|Externally generated hot gas ([[Petrosix]] process)
|memorandum of understanding
|
|----
|[[Petrochina]]<ref name=emd/>
|China
|Internal combustion ([[Fushun process|Modified Fushun process]])
|Operational
|
|----
|[[Queensland Energy Resources]]
|Stuart Deposit, [[Queensland]], [[Australia]]
|Externally generated hot gas ([[Paraho process|Paraho Indirect process]])
|Completed two-year technology demonstration program producing ultra-low sulphur diesel and aviation fuel
|
|----
|[[Questerre Energy]]<ref name=emd/>
| Isfir Al Mahatta, Jordan
|Conduction through a wall (EcoShale In-Capsule Process)
|Memorandum of understanding
|
|----
|[[Red Leaf Resources]]<ref name=emd/>
|Utah, USA
|Conduction through a wall (EcoShale In-Capsule Process)
|Under construction
|
|----
|[[San Leon Energy]]<ref name=emd/>
|[[Tarfaya]] and [[Timahdit]], Morocco
|True ''in-situ'' process (IGE process)<br/>Hot recycled solids ([[Galoter process|Enefit process]])
|Memorandum of understanding
|
|----
|[[Saniang Coal Company]]<ref name=emd/>
|China
|Internal combustion ([[Fushun process|Modified Fushun process]])
|Operational
|
|----
|Saudi Arabian International Corporation for Oil Shale Investment<ref name=jordan2/><ref name=emd/>
|El Lajjun and Attarat Umm Ghudran, Jordan
|Hot recycled solids ([[Galoter process]])
|Memorandum of understanding
|
|----
|[[Shale Technologies (company)|Shale Technologies LLC]]<ref name=emd/>
|[[Rifle, Colorado]], USA
|Internal combustion ([[Paraho process|Paraho Direct process]])
|Pilot project
|
|----
|[[TAQA]]<ref name=emd/>
|[[Timahdit]], Morocco
|Conduction through a wall (EcoShale In-Capsule Process)
|
|
|----
|[[TomCo Energy]]
|Utah, USA
|Conduction through a wall (EcoShale In-Capsule Process)
|Planning and permitting
|
|----
|[[Uzbekneftegaz]]
|[[Sangruntau oil shale deposit|Sangruntau]], Uzbekistan
|Hot recycled solids ([[Galoter process]])<ref name=utt201112>{{Cite news
| title = Atomenergoproekt have completed a conceptual design for 8 x UTT-3000 complex in Uzbekistan (Sangruntau)
| url = http://galoter.com/news/view_30/
| work = TTU Ltd.
| date = 2012-11-20
| accessdate = 2013-03-31
| archive-url = https://web.archive.org/web/20140309184035/http://galoter.com/news/view_30/
| archive-date = 2014-03-09
| url-status = dead
}} {{Webarchive|url=https://web.archive.org/web/20140309184035/http://galoter.com/news/view_30/ |date=2014-03-09 }}</ref><ref name=azer130513>
{{Cite news
| title= Oil-shale processing plant to be commissioned in Uzbekistan by 2016
| url = http://www.azernews.az/oil_and_gas/54032.html
| work = AzerNews
| first = Aynur | last = Jafarova
| date= 2013-05-13
| accessdate = 2014-03-10}}
</ref>
|Postponed<ref name=trend291215>{{Cite news
| title = Uzbekistan postpones shale plant construction
| url = http://www.oananews.org/content/news/bussiness/uzbekistan-postpones-shale-plant-construction
| work = [[Trend News Agency]]
| date = 2015-12-29
| accessdate = 2016-03-25
| archive-date = 2016-03-07
| archive-url = https://web.archive.org/web/20160307160212/http://www.oananews.org/content/news/bussiness/uzbekistan-postpones-shale-plant-construction
| url-status = dead
}}</ref>
|
|----
|[[Viru Keemia Grupp|VKG Oil]]<ref name=emd/>
|[[Kohtla-Järve]], [[Estonia]]
|Internal combustion ([[Kiviter process]])<br/>Hot solids recycle ([[Galoter process|Petroter process]])<ref name=emd/>
|Operational
|2.2 million barrels<br/>(2011).<ref name=vkg>
{{Cite journal
| title = Yearbook 2011
| publisher = Viru Keemia Grupp AS
| year = 2011
| url = http://www.vkg.ee/cms-data/upload/juhatus/yearbook-2011.pdf
| format= PDF
| accessdate =2012-05-16}}
</ref>
|----
|Whitehorn Resources Inc.<ref name=emd/>
|Wadi Abu Al Hamam, Jordan
|Conduction through a wall (EcoShale In-Capsule Process)
|Memorandum of understanding
|
|----
|Yaojie Coal Electricity Group
|[[Gansu]], China
|Internal combustion ([[Gas combustion retort process|Gas combustion retort]])
|Operational<ref name=li/>
|
|}
==Other industrial uses==
Oil shale is used for [[cement]] production by Kunda Nordic Cement in Estonia, by Holcim in Germany, and by [[Fushun]] cement factory in China.<ref name=wec/><ref name=koel>
{{Cite journal
| last=Koel | first=Mihkel
| title=Estonian oil shale
| journal=Oil Shale. A Scientific-Technical Journal
| publisher=Estonian Academy Publishers
| issue=Extra
| year=1999
| url=http://www.kirj.ee/public/oilshale/Est-OS.htm
| issn = 0208-189X
| accessdate=2007-07-21}}
</ref> Oil shale can also be used for production of different chemical products, construction materials, and pharmaceutical products, e.g. [[ammonium bituminosulfonate]].<ref name=eu>{{cite journal
| publisher = European Academies Science Advisory Council
| url = https://www.easac.org/fileadmin/PDF_s/reports_statements/Study.pdf
| title = A study on the EU oil shale industry viewed in the light of the Estonian experience. A report by EASAC to the Committee on Industry, Research and Energy of the European Parliament
| last1 = Francu
| first1 = Juraj
| last2 = Harvie
| first2 = Barbra
| last3 = Laenen
| first3 = Ben
| last4 = Siirde
| first4 = Andres
| last5 = Veiderma
| first5 = Mihkel
| pages = 12–13; 18–19; 23–24; 28
| format = PDF
| date = May 2007
| accessdate = 2010-06-21
| journal =
| archive-url = https://web.archive.org/web/20110726011345/http://www.easac.org/fileadmin/PDF_s/reports_statements/Study.pdf
| archive-date = 2011-07-26
| url-status = dead
}}</ref><ref name=alali>{{Cite conference
|last = Alali
|first = Jamal
|title = Jordan Oil Shale, Availability, Distribution, And Investment Opportunity
|publisher = International Oil Shale Conference
|place = Amman, Jordan
|url = http://www.sdnp.jo/International_Oil_Conference/rtos-A117.pdf
|format = PDF
|date = 2006-11-07
|accessdate = 2008-03-04
|conference =
|archive-url = https://web.archive.org/web/20080527234121/http://www.sdnp.jo/International_Oil_Conference/rtos-A117.pdf
|archive-date = 2008-05-27
|url-status = dead
}}</ref> However, use of oil shale for production of these products is still very rare and in experimental stages only.<ref name=wec/><ref name=dyni/>
Some oil shales are suitable source for sulfur, ammonia, alumina, soda ash, and nahcolite which occur as shale oil extraction byproducts. Some oil shales can also be used for uranium and other rare chemical element production. During 1946–1952, a marine variety of [[Basidiolichen|Dictyonema]] shale was used for [[uranium]] production in [[Sillamäe]], Estonia, and during 1950–1989 alum shale was used in Sweden for the same purpose.<ref name=dyni/> [[Oil shale gas]] can also be used as a substitute for natural gas. After [[World War II]], Estonian-produced oil shale gas was used in [[Leningrad]] and the cities in North Estonia.<ref>{{cite web
| publisher= [[Viru Keemia Grupp]]
| url= https://www.vkg.ee/eng/company/history
| title= History of the company
| accessdate= 2007-07-21
| archive-date= 2018-02-05
| archive-url= https://web.archive.org/web/20180205001504/https://www.vkg.ee/eng/company/history
| url-status= dead
}}</ref> However, at the current price level of natural gas, this is not economically feasible.<ref name=ttu>{{Cite web
| author = Ingo Valgma
| title = Map of oil shale mining history in Estonia
| publisher = Mining Institute of [[Tallinn University of Technology]]
| url = http://www.ene.ttu.ee/maeinstituut/mgis/mapofhistory.htm
| accessdate = 2007-07-21
| archive-date = 2014-08-17
| archive-url = https://web.archive.org/web/20140817092025/http://www.ene.ttu.ee/maeinstituut/mgis/mapofhistory.htm
| url-status = dead
}}</ref><ref name=proceedings>
{{Cite journal
| last = Schora | first = F. C.
| last2 = Tarman | first2 = P. B.
| last3 = Feldkirchner | first3 = H. L.
| last4 = Weil | first4 = S. A.
| title = Hydrocarbon fuels from oil shale
| journal = Proceedings
| publisher = American Institute of Chemical Engineers
| volume = 1
| pages = 325–330
| year = 1976 | id = A77-12662 02-44}}
</ref>
==Economics==
{{main|Oil shale economics}}
[[File:WTI price 96 09.PNG|thumb|right|alt= A graph of [[NYMEX]] [[sweet crude oil|light-sweet crude oil]] price changes from 1996 to 2009 (not adjusted for inflation). In 1996, the price was about US$20 per barrel. Since then, the prices saw a sharp rise, peaking at over $140 per barrel in 2008. It dropped to about $70 per barrel in mid 2009.|[[NYMEX]] [[sweet crude oil|light-sweet crude oil]] prices 1996–2009 (not adjusted for inflation)]]
The amount of economically recoverable oil shale is unknown.<ref name=weo2010>
{{Cite book
| title= World Energy Outlook 2010
| author = [[International Energy Agency|IEA]]
| publisher = [[Organisation for Economic Co-operation and Development|OECD]]
| location = [[Paris]]
| pages = 165–169
| year = 2010
| isbn = 978-92-64-08624-1}}
</ref> The various attempts to develop oil shale deposits have succeeded only when the cost of shale-oil production in a given region comes in below the price of crude oil or its other substitutes.<ref>
{{cite journal
| author = Rapier, Robert
| title = Oil Shale Development Imminent
| publisher = R-Squared Energy Blog
| date = 2006-06-12
| url = http://i-r-squared.blogspot.com/2006/06/oil-shale-development-imminent.html
| accessdate = 2007-06-22}}
</ref>
According to a survey conducted by the [[RAND Corporation]], the cost of producing a barrel of shale oil at a hypothetical surface retorting complex in the United States (comprising a mine, retorting plant, [[upgrader|upgrading plant]], supporting utilities, and spent shale reclamation), would range between [[US dollars|US$]]70–95 ($440–600/m<sup>3</sup>), adjusted to 2005 values. Assuming a gradual increase in output after the start of commercial production, the analysis projects a gradual reduction in processing costs to $30–40 per barrel ($190–250/m<sup>3</sup>) after achieving the milestone of {{convert|1|Goilbbl|sp=us}}.<ref name="eu" /><ref name=rand /> [[Royal Dutch Shell]] has announced that its [[Shell in situ conversion process|Shell ICP technology]] would realize a profit when crude oil prices are higher than $30 per barrel ($190/m<sup>3</sup>), while some technologies at full-scale production assert profitability at oil prices even lower than $20 per barrel ($130/m<sup>3</sup>).<ref name=schmidt>
{{Cite journal
| last = Schmidt | first = S. J.
| title = New directions for shale oil:path to a secure new oil supply well into this century: on the example of Australia
| journal =[[Oil Shale (journal)|Oil Shale. A Scientific-Technical Journal]]
| publisher = Estonian Academy Publishers
| volume =20
| issue =3
| pages =333–346
| year = 2003
| url = http://www.kirj.ee/public/oilshale/7_schmidt_2003_3s.pdf
| format = PDF
| issn = 0208-189X
| accessdate = 2007-06-02}}
</ref><ref name=rockymountainnews>{{cite news
|newspaper = Rocky Mountain News
|last = Seebach
|first = Linda
|url = http://www.rockymountainnews.com/drmn/news_columnists/article/0,1299,DRMN_86_4051709,00.html
|title = Shell's ingenious approach to oil shale is pretty slick
|date = 2005-09-02
|accessdate = 2007-06-02
|archiveurl = https://web.archive.org/web/20070430224933/http://www.rockymountainnews.com/drmn/news_columnists/article/0,1299,DRMN_86_4051709,00.html
|archivedate = 2007-04-30
|url-status = dead
}} {{Webarchive|url=https://web.archive.org/web/20070430224933/http://www.rockymountainnews.com/drmn/news_columnists/article/0,1299,DRMN_86_4051709,00.html |date=2007-04-30 }}</ref><ref name=fossilenergy>
{{Cite journal
| last=Johnson | first=Harry R.
| last2=Crawford | first2=Peter M.
| last3=Bunger | first3=James W.
| title =Strategic Significance of America's Oil Shale Resource. Volume II Oil Shale Resources, Technology and Economics
| publisher = United States Department of Energy
| date = March 2004
| url= http://ds.heavyoil.utah.edu/dspace/bitstream/123456789/10015/1/StrategicsignificanceofAmericasoilshaleresource_Vol2.pdf
| format = PDF
| accessdate =2014-02-09
| archiveurl = https://web.archive.org/web/20140221152437/http://ds.heavyoil.utah.edu/dspace/bitstream/123456789/10015/1/StrategicsignificanceofAmericasoilshaleresource_Vol2.pdf
| archivedate = 2014-02-21
| url-status = dead}}
</ref>
To increase the efficiency of oil shale retorting and by this the viability of the shale oil production, researchers have proposed and tested several co-pyrolysis processes, in which other materials such as [[biomass]], [[peat]], waste [[bitumen]], or [[rubber]] and [[plastic]] wastes are retorted along with the oil shale.<ref name=co-pyrolisis>
{{Cite journal
| title =Co-pyrolysis of waste plastics with oil shale
| last = Tiikma | first=Laine
| last2 = Johannes | first2=Ille
| last3 = Pryadka | first3=Natalja
| year = 2002
| journal = Proceedings. Symposium on Oil Shale 2002, Tallinn, Estonia
| page = 76}}
</ref><ref name=co-pyrolisis2>
{{Cite journal
| title =Fixation of chlorine evolved in pyrolysis of PVC waste by Estonian oil shales
| last = Tiikma | first=Laine
| last2 = Johannes | first2=Ille
| last3 = Luik | first3=Hans
| journal=Journal of Analytical and Applied Pyrolysis
|date=March 2006
| volume=75
| issue=2
| pages=205–210
| doi =10.1016/j.jaap.2005.06.001}}
</ref><ref name=veski1>
{{Cite journal
| last =Veski | first =R.
| last2 =Palu | first2 =V.
| last3 =Kruusement | first3 =K.
| title =Co-liquefaction of kukersite oil shale and pine wood in supercritical water
| journal =[[Oil Shale (journal)|Oil Shale. A Scientific-Technical Journal]]
| publisher = Estonian Academy Publishers
| volume =23
| issue =3
| pages =236–248
| year =2006
| url=http://www.kirj.ee/public/oilshale/oil-2006-3-4.pdf
| format = PDF
| issn = 0208-189X
| accessdate =2007-06-16}}
</ref><ref name=Morocco>
{{Cite journal
| last = Aboulkas | first =A.
| last2 =El Harfi | first2 =K.
| last3 =El Bouadili | first3 =A.
| last4 =Benchanaa | first4 =M.
| last5 =Mokhlisse | first5 =A.
| last6 =Outzourit | first6 =A.
| title = Kinetics of co-pyrolysis of Tarfaya (Morocco) oil shale with high-density polyethylene
| journal =[[Oil Shale (journal)|Oil Shale. A Scientific-Technical Journal]]
| publisher = Estonian Academy Publishers
| volume =24
| issue =1
| pages =15–33
| year =2007
| url=http://www.kirj.ee/public/oilshale/oil-2006-3-4.pdf
| format = PDF
| issn = 0208-189X
| accessdate =2007-06-16}}
</ref><ref name=turkey2>{{Cite conference
| last = Ozdemir
| first = M.
| last2 = A. Akar, A. Aydoğan, E. Kalafatoglu; E. Ekinci
| title = Copyrolysis of Goynuk oil shale and thermoplastics
| publisher = Jordanian Natural Resources Authority
| conference = International Oil Shale Conference
| date = 2006-11-07
| location = [[Amman]], [[Jordan]]
| url = http://www.sdnp.jo/International_Oil_Conference/rtos-A114.pdf
| format = PDF
| accessdate = 2007-06-29
| archive-url = https://web.archive.org/web/20080527234130/http://www.sdnp.jo/International_Oil_Conference/rtos-A114.pdf
| archive-date = 2008-05-27
| url-status = dead
}}</ref> Some modified technologies propose combining a fluidized bed retort with a circulated fluidized bed furnace for burning the by-products of pyrolysis (char and oil shale gas) and thereby improving oil yield, increasing throughput, and decreasing retorting time.<ref name=siirde>
{{Cite conference
|last1=Siirde
|first1=Andres
|last2=Martins
|first2=Ants
|title=Oil shale fluidized bed retorting technology with CFB furnace for burning the by-products
|publisher=[[Tallinn University of Technology]]
|conference=International Oil Shale Symphosium
|date=2009-06-07
|location=[[Tallinn]], [[Estonia]]
|url=http://www.oilshalesymposium.com/fileadmin/user_upload/documents/SIIRDE.pdf
|format=PDF
|accessdate=2009-05-22
|url-status=dead
|archiveurl=https://web.archive.org/web/20120224021635/http://www.oilshalesymposium.com/fileadmin/user_upload/documents/SIIRDE.pdf
|archivedate=February 24, 2012
}}</ref>
In a 1972 publication by the journal ''Pétrole Informations'' (ISSN 0755-561X), shale oil production was unfavorably compared to the [[coal liquefaction]]. The article stated that coal liquefaction was less expensive, generated more oil, and created fewer environmental impacts than oil shale extraction. It cited a conversion ratio of {{convert|650|L|U.S.gal impgal}} of oil per one [[tonne]] of coal, as against {{convert|150|L|U.S.gal impgal}} of shale oil per one tonne of oil shale.<ref name=laherrere/>
A critical measure of the viability of oil shale as an energy source lies in the ratio of the energy produced by the shale to the energy used in its mining and processing, a ratio known as "Energy Returned on Energy Invested" ([[EROEI]]). A 1984 study estimated the EROEI of the various known oil-shale deposits as varying between 0.7–13.3<ref name=science2>
{{Cite journal
| last = Cleveland | first = Cutler J.
| last2 = Costanza | first2 = Robert
| last3 = Hall | first3 = Charles A. S.
| last4 = Kaufmann | first4 = Robert
| title =Energy and the U.S. Economy: A Biophysical Perspective
| journal = [[Science (journal)|Science]]
| publisher = [[American Association for the Advancement of Science]]
| volume = 225
| issue = 4665
| pages =890–897
| date = 1984-08-31
| issn = 0036-8075
| doi =10.1126/science.225.4665.890
| pmid =17779848| bibcode = 1984Sci...225..890C}}
</ref>
although known oil-shale extraction development projects assert an EROEI between 3 and 10. According to the [[World Energy Outlook|World Energy Outlook 2010]], the EROEI of ''ex-situ'' processing is typically 4 to 5 while of ''in-situ'' processing it may be even as low as 2. However, according to the IEA most of used energy can be provided by burning the spent shale or oil-shale gas.<ref name=weo2010/>
The water needed in the oil shale retorting process offers an additional economic consideration: this may pose a problem in areas with water scarcity.
==Environmental considerations==
{{Main|Environmental impact of the oil shale industry}}
Mining oil shale involves a number of environmental impacts, more pronounced in surface mining than in underground mining.<ref name=mittal>
{{cite web
| last =Mittal | first =Anu K.
| title = Unconventional Oil and Gas Production. Opportunities and Challenges of Oil Shale Development
| publisher = [[Government Accountability Office]]
| date =10 May 2012
| url = https://www.gao.gov/assets/600/590761.pdf
| format = PDF
| accessdate = 22 December 2012}}
</ref> These include acid drainage induced by the sudden rapid exposure and subsequent [[oxidation]] of formerly buried materials, the introduction of metals including [[Mercury (element)|mercury]]<ref>Western Oil Shale Has a High Mercury Content http://www.westernresearch.org/uploadedFiles/Energy_and_Environmental_Technology/Unconventional_Fuels/Oil_Shale/MercuryinOilShale.pdf {{Webarchive|url=https://web.archive.org/web/20110719102244/http://www.westernresearch.org/uploadedFiles/Energy_and_Environmental_Technology/Unconventional_Fuels/Oil_Shale/MercuryinOilShale.pdf |date=2011-07-19 }}</ref> into surface-water and groundwater, increased [[erosion]], sulfur-gas emissions, and air pollution caused by the production of [[Atmospheric particulate matter|particulates]] during processing, transport, and support activities.<ref name="Burnham">{{cite journal
| author = Burnham, A. K.
| title = Slow Radio-Frequency Processing of Large Oil Shale Volumes to Produce Petroleum-like Shale Oil
| publisher = Lawrence Livermore National Laboratory
| date = 2003-08-20
| url = https://e-reports-ext.llnl.gov/pdf/243505.pdf
| format = PDF
| id = UCRL-ID-155045
| accessdate = 2007-06-28
| journal =
| archive-url = https://web.archive.org/web/20170216041927/https://e-reports-ext.llnl.gov/pdf/243505.pdf
| archive-date = 2017-02-16
| url-status = dead
}}</ref><ref name="openpitimpacts">
{{cite book
|title= The Abandoned Mine Site Characterization and Cleanup Handbook
|chapter = Environmental Impacts from Mining
|url= https://www.epa.gov/sites/production/files/2015-09/documents/2000_08_pdfs_amscch.pdf
|format=PDF
|pages=3/1–3/11
|publisher= [[United States Environmental Protection Agency]]
|date=August 2000
|accessdate=21 June 2010}}
</ref> In 2002, about 97% of air pollution, 86% of total waste and 23% of water pollution in Estonia came from the power industry, which uses oil shale as the main resource for its power production.<ref name=raukas>
{{Cite journal
| last = Raukas | first = Anto
| title =Opening a new decade
| journal = [[Oil Shale (journal)|Oil Shale. A Scientific-Technical Journal]]
| publisher = Estonian Academy Publishers
| volume = 21
| issue =1
| pages =1–2
| year =2004
| url=http://www.kirj.ee/public/oilshale/1_ed_page_2004_1.pdf
| format = PDF
| issn = 0208-189X
| accessdate =14 May 2008}}
</ref>
Oil-shale extraction can damage the biological and recreational value of land and the ecosystem in the mining area. Combustion and thermal processing generate waste material. In addition, the atmospheric emissions from oil shale processing and combustion include [[carbon dioxide]], a [[greenhouse gas]]. Environmentalists oppose production and usage of oil shale, as it creates even more greenhouse gases than conventional fossil fuels.<ref name="nrdc">
{{cite journal
|title= Driving It Home. Choosing the Right Path for Fueling North America's Transportation Future
|url= https://www.nrdc.org/sites/default/files/drivingithome.pdf
|format= PDF
|publisher= [[Natural Resources Defense Council]]
|date= June 2007
|accessdate= 19 April 2008
}}</ref> Experimental ''in situ'' conversion processes and [[carbon capture and storage]] technologies may reduce some of these concerns in the future, but at the same time they may cause other problems, including [[groundwater pollution]].<ref name="BartisBoston">
{{cite conference
| url=http://www.aspo-usa.com/fall2006/presentations/pdf/Bartis_J_Boston_2006.pdf
| format = PDF
| conference = World Oil Conference
| title=Unconventional Liquid Fuels Overview
| last = Bartis | first = Jim
| publisher=Association for the Study of Peak Oil & Gas – USA
| date= 26 October 2006
| accessdate=28 June 2007
| archiveurl = https://web.archive.org/web/20110721161801/http://www.aspo-usa.com/fall2006/presentations/pdf/Bartis_J_Boston_2006.pdf
| archivedate = 21 July 2011
| url-status = dead}}
</ref> Among the water contaminants commonly associated with oil shale processing are oxygen and nitrogen heterocyclic hydrocarbons. Commonly detected examples include [[quinoline]] derivatives, [[pyridine]], and various alkyl homologues of pyridine ([[picoline]], [[lutidine]]).<ref>Sims, G. K. and E.J. O'Loughlin. 1989. Degradation of pyridines in the environment. CRC Critical Reviews in Environmental Control. 19(4): 309-340.</ref>
Water concerns are sensitive issues in arid regions, such as the western US and Israel's [[Negev|Negev Desert]], where plans exist to expand oil-shale extraction despite a water shortage.<ref>{{cite news
|url = http://www.deseretnews.com/article/695263708/Oil-shale-rush-is-sparking-concern.html
|title = Oil-shale 'rush' is sparking concern
|last = Speckman
|first = Stephen
|newspaper = [[Deseret Morning News]]
|date = 22 March 2008
|accessdate = 6 May 2011
|archive-date = 16 November 2010
|archive-url = https://web.archive.org/web/20101116001214/http://www.deseretnews.com/article/695263708/Oil-shale-rush-is-sparking-concern.html
|url-status = dead
}} {{Webarchive|url=https://web.archive.org/web/20101116001214/http://www.deseretnews.com/article/695263708/Oil-shale-rush-is-sparking-concern.html |date=16 November 2010 }}</ref> Depending on technology, above-ground retorting uses between one and five barrels of water per barrel of produced shale-oil.<ref name=rand/><ref name=BLM2008/><ref name=water>
{{cite web
| title = Critics charge energy, water needs of oil shale could harm environment
| publisher = U.S. Water News Online
|date=July 2007
| url = http://www.uswaternews.com/archives/arcsupply/7critchar7.html
| accessdate = 1 April 2008
| archiveurl = https://web.archive.org/web/20080618074850/http://www.uswaternews.com/archives/arcsupply/7critchar7.html
| archivedate = 18 June 2008}}
</ref><ref name=Al-Ayed>
{{cite web
| title = Jordan Oil Shale Project
| last = Al-Ayed | first = Omar
| publisher = [[Al-Balqa` Applied University]]
| year = 2008
| url = http://www.jordanoilshale.net/page4.aspx
| accessdate = 15 August 2008
| archiveurl = https://web.archive.org/web/20080603231353/http://www.jordanoilshale.net/page4.aspx
| archivedate = 3 June 2008}}
</ref> A 2008 programmatic [[environmental impact statement]] issued by the US [[Bureau of Land Management]] stated that surface mining and retort operations produce {{convert|2|to|10|USgal|sp=us}} of waste water per {{convert|1|ST|sp=us}} of processed oil shale.<ref name=BLM2008>{{cite book
|url=http://ostseis.anl.gov/documents/fpeis/vol1/OSTS_FPEIS_Vol1_Ch4.pdf
|title=Proposed Oil Shale and Tar Sands Resource Management Plan Amendments to Address Land Use Allocations in Colorado, Utah, and Wyoming and Final Programmatic Environmental Impact Statement
|chapter=Chapter 4. Effects of Oil Shale Technologies
|format=PDF
|pages=4‑3
|publisher=[[Bureau of Land Management]]
|date=September 2008
|accessdate=7 August 2010
|id=FES 08-32
|archive-url=https://web.archive.org/web/20100527214525/http://ostseis.anl.gov/documents/fpeis/vol1/OSTS_FPEIS_Vol1_Ch4.pdf
|archive-date=27 May 2010
|url-status=dead
}}</ref> ''In situ'' processing, according to one estimate, uses about one-tenth as much water.<ref>
{{cite journal
| title=Hopes for shale oil are revived
| last = Fischer | first = Perry A.
| url=http://www.worldoil.com/magazine/MAGAZINE_DETAIL.asp?ART_ID=2658&MONTH_YEAR=Aug-2005
| journal = World Oil Magazine
| publisher= [[Gulf Publishing Company]]
|date=August 2005
| archiveurl = https://web.archive.org/web/20061109140826/http://worldoil.com/magazine/MAGAZINE_DETAIL.asp?ART_ID=2658&MONTH_YEAR=Aug-2005
| archivedate = 9 November 2006
| accessdate=1 April 2008}}
</ref>
[[environmentalism|Environmental]] activists, including members of [[Greenpeace]], have organized strong protests against the oil shale industry. In one result, [[Queensland Energy Resources]] put the proposed [[Stuart Oil Shale Project]] in Australia on hold in 2004.<ref name="Burnham" /><ref>
{{cite web
| url=http://www.abc.net.au/news/2004-07-22/greenpeace-happy-with-part-closure-of-shale-oil/2013172
| title=Greenpeace happy with part closure of shale oil plant
| publisher=[[Australian Broadcasting Corporation]]
| date=22 July 2004
| accessdate=19 May 2008
}}</ref><ref>
{{cite conference
| url=http://www.smedg.org.au/miningc/SMCMay02text.pdf
| format=PDF
| title=Greenpeace vs the future of Australian oil shale
| last = Anderson | first = Campbell
| conference = The 53rd Sydney Mining Club
| location=Sydney
| date= 2 May 2002
| accessdate=10 April 2009}}
</ref>
==See also==
* [[Oil shale reserves]]
==References==
{{reflist|30em}}
{{Industries}}
{{good article}}
{{DEFAULTSORT:Oil Shale Industry}}
[[Category:17th-century introductions]]
[[Category:Oil shale|Industry]]
[[Category:Petroleum industry]]
[[Category:Mining]]
[[Category:Energy industry]]<noinclude>
<small>This page was moved from [[:en:Oil shale industry]]. Its edit history can be viewed at [[Oil shale industry/edithistory]]</small></noinclude>
osb6fxo0pfi8bneq2c9im6iuqm6nx6b
Overlord: Minions
0
113630
769851
541091
2026-10-23T20:08:50Z
InternetArchiveBot
34092
Rescuing 1 sources and tagging 0 as dead. #IABot (v2.0.9.6)
769851
wikitext
text/x-wiki
{{update|date=November 2010}}
{{Infobox video game
|title = Overlord: Minions
|image = OverlordMinions.jpg
|caption = North American cover
|developer = [[Climax Studios|Climax Action]]
|publisher = [[Codemasters]]
|designer =
|engine =
|series = ''[[Overlord (video game series)|Overlord]]''
|released = {{Vgrelease|NA|23 June 2009|EU|26 June 2009|AUS|2 July 2009<ref>{{cite web|url=http://www.gamefaqs.com/ds/952374-overlord-minions/data |title=Overlord Minions Release Information for DS |website=[[GameFAQs]] |accessdate=April 17, 2015}}</ref>}}
|genre = [[Puzzle video game|Puzzle]] [[Action game|action]]
|modes = [[Single-player]]
|composer = [[Allister Brimble]]
|platforms = [[Nintendo DS]]
}}
'''''Overlord: Minions''''' is a 2009 [[Puzzle video game|puzzle]] [[Action game|action]]<ref>{{cite web |url=http://www.codemasters.com/uk/minions/nintendods/info/ |title=Codemasters - Overlord™: Minions |publisher=[[Codemasters]] |accessdate=March 30, 2013 |archive-date=January 1, 2013 |archive-url=https://web.archive.org/web/20130101211532/http://www.codemasters.com/uk/minions/nintendods/info/ |url-status=dead }}</ref> video game developed by [[Climax Studios|Climax Action]] and published by [[Codemasters]] for the [[Nintendo DS]]. It was announced August 14, 2008 alongside addition franchise expansions ''[[Overlord II]]'' and ''[[Overlord: Dark Legend]]''.<ref>{{cite magazine |last=Graft |first=Kris |date=August 14, 2008 |url=http://www.edge-online.com/news/three-more-overlords/ |title=Three More Overlords |magazine=[[Edge (magazine)|Edge]] |archiveurl=https://web.archive.org/web/20130403153920/http://www.edge-online.com/news/three-more-overlords/ |archivedate=April 3, 2013 |url-status=dead |accessdate=April 17, 2015}}</ref>
It features the minions from the 2007 ''[[Overlord (2007 video game)|Overlord]]'' [[video game]] as the primary protagonists, tasked with the mission to solve puzzles and fight enemies. They are controlled by the DS stylus. There are also items to find in the levels and a timer of how fast the player in finish. The minions can be controlled directly by the player, unlike the original game where the player controlled them by controlling an eponymous Overlord-character. Controlling the Special Forces team of four, players negotiate fiendish levels and take on a huge range of warped enemies to hunt down the Kindred, a cult dedicated to resurrecting the mighty Dragon Kin, a race of humanoid dragon hybrids, determined to replace the Overlord's test despotism with their own.
There are only four minions from four types, which makes all of them specialized in skills, for example Giblet, a brown minion from the first Overlord game, is the strongest in melee combat, Blaze is a wild pyromaniac, which grants him the ability to sling fireballs, Stench is the most stealthy of the bunch, and Zap can swim/heal.
== Plot ==
Giblet, Blaze, Stench, and Zap serve as Overlord Gromgard's elite Minion squad. Due to their specialized training and independence, they are sent to eliminate any outside opposition to their Overlord's reign.
Their first mission is to deal with mysterious fungus growth in the neighboring Withering Woods. As they fight through the woods, they find that the fungal ooze is not only infecting plants, but the Overlord's Human subjects as well. They finally discover the contamination: a sentient fungus, Globulous. After Globulous is killed, the minions are sent to enter the massive pipes which fed the fungus.
The sewer pipes lead to the Halfling town, Briarthorn Burrows. The squad discovers that the threat to their Master's dominion is the mysterious Kindred cult. The cult desires to bring back the extinct Dragon-Kin, an extinct Human-Dragon race. After tearing through Briarthorn Burrows, the Minions confronts Grub, the Halfling, Kindred lieutenant. The monstrous halfling was easily dispatched by the squad.
The elite Minions enter Cloudland Keep at the top of Shimmer Mountain. They eliminate Lady Opal, the crystalline Kindred Lieutenant who commands the human contingent of the cult.
The Minions advance to the Fossilized Temple of the Dragon-Kin, where the Minions face heavy opposition from the Dwarves. The squad pushes through the Dwarven defense, and encounter another Lieutenant, Duggen. Despite his immense size and strength as a mutant Dwarf, he is no match for the Minions.
The next destination for the Overlord's Minions is the Floating Forest, where the Kindred's army of Elves have established a strong position. The Elven defenders are overwhelmed and the Minions advances to fight the final Lieutenant, the dragon-elf hybrid Ash. With Ash's demise, the Overlord's forces are free to advance to the Kindred's final stronghold, Dragonspire.
In Dragonspire, the Minions encounter Silas Silvanus, the last Dragon-Kin and leader of the Kindred. Silvanus explains how he desires to restore and perfect his race. He tries to destroy the Overlord's servants, but the Dragon-Kin is mortally wounded by the squad. Before Silvanus dies, he unleashes his final experiment, Tiberius. Although he was the strongest, experimental offspring of Silvanus, Tiberius is destroyed by the Minions, bringing a permanent end to the Kindred.
{{clear}}
==Reception==
{{Video game reviews
| GR = 61%<ref name="GR">{{cite web |url=https://www.gamerankings.com/ds/952374-overlord-minions/index.html |title=Overlord Minions for DS |website=[[GameRankings]] |publisher=[[CBS Interactive]] |accessdate=April 17, 2015 |archive-url=https://web.archive.org/web/20190514211241/https://www.gamerankings.com/ds/952374-overlord-minions/index.html |archive-date=May 14, 2019 |url-status=dead}}</ref>
| MC = 58/100<ref name="MC">{{cite web |url=https://www.metacritic.com/game/ds/overlord-minions |title=Overlord Minions for DS Reviews |website=[[Metacritic]] |publisher=[[CBS Interactive]] |accessdate=April 17, 2015}}</ref>
| 1UP = D+<ref>{{cite web |last=Palacios |first=Tina |date=July 1, 2009 |url=http://www.1up.com/reviews/overlord-minions-review |title=Overlord Minions Review |website=[[1UP.com]] |accessdate=April 17, 2015 |archive-date=March 5, 2016 |archive-url=https://web.archive.org/web/20160305142204/http://www.1up.com/reviews/overlord-minions-review |url-status=dead }}</ref>
| Destruct = 7/10<ref>{{cite web |last=Sterling |first=Jim |date=July 12, 2009 |url=http://www.destructoid.com/review-overlord-minions-139403.phtml |title=Review: Overlord: Minions |website=[[Destructoid]] |accessdate=April 17, 2015}}</ref>
| GamePro = {{Rating|3.5|5}}<ref>{{cite magazine |last=O'Neill |first=Chelsea |date=August 10, 2009 |url=http://www.gamepro.com/article/reviews/211609/overlord-minions |title=Overlord Minions |magazine=[[GamePro]] |archiveurl=https://web.archive.org/web/20090814203854/http://www.gamepro.com/article/reviews/211609/overlord-minions |archivedate=August 14, 2009 |url-status=dead |accessdate=April 17, 2015}}</ref>
| GSpot = 4.5/10<ref>{{cite web |last=Stella |first=Shiva |date=July 20, 2009 |url=http://www.gamespot.com/reviews/overlord-minions-review/1900-6213659/ |title=Overlord: Minions Review |website=[[GameSpot]] |accessdate=April 17, 2015}}</ref>
| GRadar = 55%<ref>{{cite journal |last=Martyn |first=Charlotte |url=http://www.gamesradar.com/overlord-minions-review/ |title=Overlord: Minions review |magazine=[[Nintendo Gamer]] ([[GamesRadar]]) |date=August 2009 |page=77 |accessdate=April 17, 2015}}</ref>
| GameZone = 6/10<ref>{{cite web |last=Valentino |first=Nick |date=July 19, 2009 |url=http://www.gamezone.com/reviews/overlord_minions_nds_review |title=Overlord Minions - NDS - Review |publisher=GameZone |archiveurl=https://web.archive.org/web/20090724014629/http://nds.gamezone.com/gzreviews/r36258.htm |archivedate=July 24, 2009 |url-status=live |accessdate=April 17, 2015}}</ref>
| IGN = 6.1/10<ref>{{cite web |last=Harris |first=Craig |date=June 26, 2009 |url=http://www.ign.com/articles/2009/06/26/overlord-minions-review |title=Overlord Minions Review |website=[[IGN]] |accessdate=April 17, 2015}}</ref>
| NP = 7/10<ref>{{cite journal |title=Overlord Minions |magazine=[[Nintendo Power]] |date=August 2009 |page=91}}</ref>
| NWR = 7/10<ref>{{cite web |last=Miller |first=Zachary |date=July 7, 2009 |url=http://www.nintendoworldreport.com/review/18996/overlord-minions-nintendo-ds |title=Overlord Minions |publisher=Nintendo World Report |accessdate=April 17, 2015}}</ref>
| ONM = 51%<ref>{{cite journal |title=Overlord Minions |magazine=[[Official Nintendo Magazine]] |date=August 2009 |page=96}}</ref>
}}
A ''[[Eurogamer]]'' preview of the title observed the debt to ''[[The Lost Vikings]]'' video game, and claimed the developers had sensibly revised the genre and gameplay to be more relevant to the host console.<ref>{{cite web |last=Walker |first=John |date=May 6, 2009 |url=http://www.eurogamer.net/articles/overlord-dark-legend-overlord-minions-preview?page=2 |title=Overlord: Dark Legend, Overlord: Minions (Page 2) |website=[[Eurogamer]]}}</ref>
The game was met with mixed reception upon release, as [[GameRankings]] gave it a score of 61%,<ref name="GR" /> while [[Metacritic]] gave it 58 out of 100.<ref name="MC" />
{{clear}}
==References==
{{reflist}}
==External links==
*{{Official website|http://www.codemasters.com/uk/minions/nintendods/|name=Codemasters - Overlord™: Minions}}
*{{moby game|id=/nintendo-ds/overlord-minions}}
{{Overlord series}}
[[Category:2009 video games]]
[[Category:Climax Group games]]
[[Category:Codemasters games]]
[[Category:Nintendo DS games]]
[[Category:Nintendo DS-only games]]
[[Category:Overlord (video game series)]]
[[Category:Puzzle video games]]
[[Category:Video games developed in the United Kingdom]]
[[Category:Video games set in the Middle Ages]]
[[Category:Action video games]]
[[Category:Video games scored by Allister Brimble]]
[[Category:Video games scored by Matthew Simmonds]]
<noinclude>
<small>This page was moved from [[:en:Overlord: Minions]]. Its edit history can be viewed at [[Overlord: Minions/edithistory]]</small></noinclude>
dyse2nz7j41hh6s4rsdgl4xcrcpsinv
People Capability Maturity Model
0
113758
769891
689962
2026-10-24T00:03:26Z
InternetArchiveBot
34092
Rescuing 1 sources and tagging 0 as dead. #IABot (v2.0.9.6)
769891
wikitext
text/x-wiki
{{Advert|date=July 2011}}
'''People Capability Maturity Model''' (short names: People CMM, PCMM, P-CMM) is a maturity [[framework]] that focuses on continuously improving the management and development of the [[Human capital|human assets]] of an organization. It describes an evolutionary improvement path from ad hoc, inconsistently performed practices, to a mature, disciplined, and continuously improving development of the knowledge, skills, and motivation of the workforce that enhances strategic [[Business performance management|business performance]].
Related to fields such as [[Human resources|human resources]], [[Knowdledge management|knowledge management]], and [[Organization development|organizational development]], the People CMM guides organizations in improving their processes for managing and developing their workforces. The People CMM helps organizations characterize the maturity of their workforce practices, establish a program of continuous [[workforce development]], set priorities for improvement actions, integrate workforce development with process improvement, and establish a culture of [[excellence]]. The term was promoted in 1995, published in book form in 2001, and a second edition was published in July 2009.<ref>[http://isbndb.com/d/book/the_people_cmm_a_framework_for_human_capital_management.html] {{Webarchive|url=https://web.archive.org/web/20160304194312/http://isbndb.com/d/book/the_people_cmm_a_framework_for_human_capital_management.html |date=2016-03-04 }} {{ISBN|978-0-321-55390-4}}</ref>
==Description==
The People CMM consists of five maturity levels that establish successive foundations for continuously improving individual [[Competence (human resources)|competencies]], developing effective teams, motivating improved performance, and shaping the workforce the organization needs to accomplish its future [[Business plan|business plans]]. Each maturity level is a well-defined evolutionary plateau that institutionalizes new capabilities for developing the organization's workforce. By following the maturity framework, an organization can avoid introducing workforce practices that its employees are unprepared to implement effectively.
==Structure==
[[File:pcmmStructure.PNG|thumb|right|250px|People Capability Maturity Model|link=Special:FilePath/PcmmStructure.PNG]]
The People CMM document describes the practices that constitute each of its maturity levels and information on how to apply it in guiding organizational improvements. It describes an organization's capability for developing its workforce at each maturity level. It also describes how the People CMM can be applied as a standard for assessing workforce practices and as a guide in planning and implementing improvement activities.
Version 2 of the People CMM has been designed to correct known issues in Version 1, which was released in 1995. It adds enhancements learned from five years of implementation experience and integrates the model better with [[Capability Maturity Model Integration|CMMI]] and its IPPD extensions. The primary motivation for updating the People CMM was the error in Version 1 of placing team-building activities at Maturity Level 4. The authors made this placement based on substantial [[feedback]] that it should not be placed at Maturity Level 3, as it had been in early review releases. Experience has indicated that many organizations initiate formal development of workgroups while working toward Maturity Level 3. Thus, Version 2 of the People CMM initiates process-driven workgroup development at Maturity Level 3. This change is consistent with the placing of integrated teaming activities at Maturity Level 3 of the CMMI-IPPD.
==See also==
*[[Capability Maturity Model]] (CMM)
*[[Capability Immaturity Model]] (CIMM)
*[[Capability Maturity Model Integration]] (CMMI)
==References==
{{Reflist}}
==External links==
* [http://cmmiinstitute.com/ P-CMM Official Web Site]
* [http://www.inderscience.com/search/index.php?action=record&rec_id=22624&prevQuery=&ps=10&m=or Organisational maturity and functional performance]
* [http://www.workforcematurity.nl [requires special access] P-CMM references and best practices] {{Webarchive|url=https://web.archive.org/web/20120704144844/http://www.workforcematurity.nl/ |date=2012-07-04 }}
* [https://play.google.com/store/apps/details?id=com.telerik.PCMM P-CMM Mobile App Android]
* [https://web.archive.org/web/20160306100022/https://itunes.apple.com/IN/app/id858470527?mt=8 P-CMM Mobile App Apple] [[Category:Maturity models]] [[Category:Information technology management]] <noinclude>
<small>This page was moved from [[:en:People Capability Maturity Model]]. Its edit history can be viewed at [[People Capability Maturity Model/edithistory]]</small></noinclude>
i6q0s6q8w0ptn11kt3c8hux9vdzb1ay
User:Nardog/sandbox2.js
2
118608
769852
768159
2026-10-23T20:14:40Z
Nardog
40946
769852
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
let dialog = await OO.ui.getWindowManager().getWindow('message');
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).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?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', e => {
let $label = $(e.target.labels).last();
$label.closest('.excon-mc-grid')
.find('.excon-mc-active').removeClass('excon-mc-active');
$label.addClass('excon-mc-active');
}).on('click', e => {
if (!e.target.closest('input, .excon-mc-grid label')) {
e.preventDefault();
}
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
}
dialog.text.$element.empty().append(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
fzrwyeqw4bkdt11pofrio3h1o8n2kzv
769853
769852
2026-10-23T20:35:57Z
Nardog
40946
769853
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
let dialog = await OO.ui.getWindowManager().getWindow('message');
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).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?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', e => {
let $label = $(e.target.labels).last();
$label.closest('.excon-mc-grid')
.find('.excon-mc-active').removeClass('excon-mc-active');
$label.addClass('excon-mc-active');
}).on('click', e => {
if (!e.target.closest('input, .excon-mc-grid label')) {
e.preventDefault();
}
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
}
dialog.title.$element.next().replaceWith(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
gmt3mb913jphioupbqagbt5eanbs78f
769854
769853
2026-10-23T20:37:33Z
Nardog
40946
769854
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
let dialog = await OO.ui.getWindowManager().getWindow('message');
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).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?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', e => {
let $label = $(e.target.labels).last();
$label.closest('.excon-mc-grid')
.find('.excon-mc-active').removeClass('excon-mc-active');
$label.addClass('excon-mc-active');
}).on('click', e => {
if (!e.target.closest('input, .excon-mc-grid label')) {
e.preventDefault();
}
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.next().replaceWith(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
lorq8k107r0fvsw6pmewk1xpmj08xez
769855
769854
2026-10-23T20:42:04Z
Nardog
40946
769855
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
let dialog = await OO.ui.getWindowManager().getWindow('message');
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).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?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', e => {
let $label = $(e.target.labels);
$label.closest('.excon-mc-grid')
.find('.excon-mc-active').removeClass('excon-mc-active');
$label.addClass('excon-mc-active');
}).on('click', e => {
if (!e.target.closest('input, .excon-mc-grid label')) {
e.preventDefault();
}
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.next().replaceWith(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
nckczrq58095j1txj8wdnn60l3v9xg3
769856
769855
2026-10-23T20:42:10Z
Nardog
40946
769856
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
let dialog = await OO.ui.getWindowManager().getWindow('message');
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).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?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', e => {
let $label = $(e.target.labels[0]);
$label.closest('.excon-mc-grid')
.find('.excon-mc-active').removeClass('excon-mc-active');
$label.addClass('excon-mc-active');
}).on('click', e => {
if (!e.target.closest('input, .excon-mc-grid label')) {
e.preventDefault();
}
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.next().replaceWith(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
833kamtvqyjysmyzo4j8grfdbtp6rdp
769857
769856
2026-10-23T20:42:24Z
Nardog
40946
769857
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
let dialog = await OO.ui.getWindowManager().getWindow('message');
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).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?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', e => {
let $label = $(e.target.labels[0]);
$label.closest('.excon-mc-grid')
.find('.excon-mc-active').removeClass('excon-mc-active');
$label.addClass('excon-mc-active');
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.next().replaceWith(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
qma8avkbyr2wqcz61gd2u687ucg3jz3
769858
769857
2026-10-23T20:51:35Z
Nardog
40946
769858
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
let dialog = await OO.ui.getWindowManager().getWindow('message');
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).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?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
$('.oo-ui-windowManager:has(.excon-mc)').remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', e => {
let $label = $(e.target.labels[0]);
$label.closest('.excon-mc-grid')
.find('.excon-mc-active').removeClass('excon-mc-active');
$label.addClass('excon-mc-active');
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.next().replaceWith(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
l7qnobxnlhpb2nvo8oqtfazfsgo2uvv
769859
769858
2026-10-23T21:06:22Z
Nardog
40946
769859
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
let dialog = await OO.ui.getWindowManager().getWindow('message');
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).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?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
$('.oo-ui-windowManager:has(.excon-mc)').remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', e => {
$(e.target).closest('.excon-mc-grid').find('label')
.removeClass('excon-mc-active')
.filter(e.target.labels).addClass('excon-mc-active');
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.next().replaceWith(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
a5cur1xb9wql2ib0sra1eb8ln1n8o1s
769860
769859
2026-10-23T21:17:13Z
Nardog
40946
769860
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
let dialog = await OO.ui.getWindowManager().getWindow('message');
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).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().getMenu().toggle(false);
});
});
instance.closed.then(result => {
if (result?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
$('.oo-ui-windowManager:has(.excon-mc)').remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', function (e) {
$(e.target).closest('.excon-mc-grid').find('label')
.removeClass('excon-mc-active')
.filter(e.target.labels).addClass('excon-mc-active');
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.next().replaceWith(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
2deev20rx5vgu26wmjzghqdntsduzx1
769861
769860
2026-10-23T21:20:11Z
Nardog
40946
769861
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
let dialog = await OO.ui.getWindowManager().getWindow('message');
let multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).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().getMenu().toggle(false);
});
});
instance.closed.then(result => {
if (result?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
$('.oo-ui-windowManager:has(.excon-mc)').remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', function (e) {
$(e.target).closest('.excon-mc-grid').find('label')
.removeClass('excon-mc-active')
.filter(e.target.labels).addClass('excon-mc-active');
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.nextAll().remove().end().after(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
nt7n22ghsnqmnhhml7y90ugthavam0k
769862
769861
2026-10-23T21:23:27Z
Nardog
40946
769862
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let dialog, multiselect;
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
if (!dialog) {
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).connect(dialog, { change: 'updateSize', reorder: 'updateSize' });
dialog.text.$element.text('Muted users:').append(multiselect.$element);
}
let instance = dialog.open({ size: 'medium' });
instance.opened.then(() => {
setTimeout(() => {
multiselect.focus().menu.toggle(false);
});
});
instance.closed.then(result => {
if (result?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
$('.oo-ui-windowManager:has(.excon-mc)').remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', function (e) {
$(e.target).closest('.excon-mc-grid').find('label')
.removeClass('excon-mc-active')
.filter(e.target.labels).addClass('excon-mc-active');
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.next().replaceWith(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
sozq6rqlniq5n0md9yi1eqzfkpt77dm
769863
769862
2026-10-23T21:23:48Z
Nardog
40946
769863
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let dialog, multiselect;
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
if (!dialog) {
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).connect(dialog, { change: 'updateSize', reorder: 'updateSize' });
dialog.text.$element.text('Muted users:').append(multiselect.$element);
}
let instance = dialog.open({ size: 'medium' });
instance.opened.then(() => {
setTimeout(() => {
multiselect.focus().getMenu().toggle(false);
});
});
instance.closed.then(result => {
if (result?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
$('.oo-ui-windowManager:has(.excon-mc)').remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', function (e) {
$(e.target).closest('.excon-mc-grid').find('label')
.removeClass('excon-mc-active')
.filter(e.target.labels).addClass('excon-mc-active');
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.next().replaceWith(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
hox7x7a7fsurkv1sxdkwqqqpbjdrey0
769864
769863
2026-10-23T21:23:56Z
Nardog
40946
769864
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let dialog, multiselect;
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
if (!dialog) {
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).connect(dialog, { change: 'updateSize', reorder: 'updateSize' });
dialog.text.$element.text('Muted users:').append(multiselect.$element);
}
let instance = dialog.open({ size: 'medium' });
instance.opened.then(() => {
setTimeout(() => {
multiselect.focus().getMenu().toggle(false);
});
});
instance.closed.then(result => {
if (result?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
$('.oo-ui-windowManager:has(.excon-mc)').remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', function (e) {
$(e.target).closest('.excon-mc-grid').find('label')
.removeClass('excon-mc-active')
.filter(e.target.labels).addClass('excon-mc-active');
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.nextAll().remove().end().after(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
9mwxy91qkww8dxeyyatsse6z50mvck4
769865
769864
2026-10-23T21:27:14Z
Nardog
40946
769865
javascript
text/javascript
/* globals isc */
(async () => {
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.getNamespaceId() === 2
)) {
return;
}
}
if ($link.is('.external, .extiw')) {
Object.assign(t, {
getMain: extGetMain,
host: this.host,
namespace: 0,
title: pn
});
} else {
if (t.getNamespaceId() < 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);
}
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:not(:empty), #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:not(:empty), .mw-newpages-pagename:empty + a',
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:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a:not(:empty), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a:not(:empty)'
},
{
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: async function (e) {
// e.preventDefault();
// let $link = $(this);
// let $wrapper = $link.parent();
// $link.detach();
// $wrapper.text('purging');
// let pn = $wrapper.closest('.listtools').data('listtools').toText();
// try {
// let response = await new mw.Api().post({
// action: 'purge',
// forcelinkupdate: 1,
// titles: pn,
// formatversion: 2
// });
// if (response.purge[0].purged) {
// mw.notify(`Purged "${pn}"'`);
// }
// } finally {
// $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 {}
$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' &&
(() => {
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';
this.init(lis);
}
async init(lis) {
await mw.loader.using('mediawiki.util');
this.groupByTitle(lis)
.flatMap(group => this.chunkByUser(group))
.flatMap(chunk => this.divideByDate(chunk))
.flatMap(subchunk => this.makeLinks(subchunk))
.forEach(([$span, parent]) => {
$span.appendTo(parent);
});
}
groupByTitle(lis) {
if (isHist) {
return [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 (!Object.hasOwn(lisByTitle, 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?.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 = Date.parse(
li.dataset.mwTs.replace(
/(....)(..)(..)(..)(..)(..)/,
'$1-$2-$3T$4:$5:$6Z'
)
) / 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 url = mw.util.getUrl(
!isHist && mw.util.getParamValue('title', firstPerma.search),
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(`${count - i}/${count}`)
),
this.isEnhanced
? li.tagName === 'TR'
? li.lastElementChild
: li.querySelector('.mw-changeslist-line-inner')
: li
]);
}
}
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')
)) {
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox8.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
}
$(() => {
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) {
window.moveHistoryDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox5.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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'
]);
});
});
});
$(() => {
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) {
window.sectionSearchDialog?.open();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox7.js&action=raw&ctype=text/javascript');
mw.loader.load([
'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', () => {
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') &&
isc('User:Nardog/AutoTestcases.js', 'en.wikipedia.org');
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox10.js&action=raw&ctype=text/javascript');
(mw.config.get('wgNamespaceNumber') === -1 || mw.config.exists('wgDiffNewId') ||
mw.config.get('wgAction') === 'history') &&
(() => {
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'], () => {
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.getNamespaceId() === 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')
)
);
});
});
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') &&
(() => {
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(() => {
mw.loader.addStyleTag('body:not(.rcmuter-disabled) .rcmuter-muted{display:none !important} .rcmuter-edit::after, .rcmuter-togglemuted::after{content:": " attr(data-rcmuter)}');
let muted = new Set(mw.user.options.get('userjs-rcmuter')?.split('|'));
let save = () => {
let ns = [...muted].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', muted.size);
};
let dialog, multiselect;
let $edit = $('<a>').attr({
class: 'rcmuter-edit',
href: '#',
role: 'button',
'data-rcmuter': muted.size
}).text('Edit muted').on('click', async e => {
e.preventDefault();
if (!dialog) {
await mw.loader.using([
'oojs-ui-windows', 'mediawiki.widgets.UsersMultiselectWidget'
]);
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
multiselect = new mw.widgets.UsersMultiselectWidget({
$overlay: dialog.$overlay,
ipAllowed: true,
selected: [...muted]
}).connect(dialog, { change: 'updateSize', reorder: 'updateSize' });
dialog.text.$element.html(
new OO.ui.FieldLayout(multiselect, {
align: 'top',
label: 'Muted users:'
}).$element
);
}
let instance = dialog.open({ size: 'medium' });
instance.opened.then(() => {
setTimeout(() => {
multiselect.focus().getMenu().toggle(false);
});
});
instance.closed.then(result => {
if (result?.action !== 'accept') return;
muted = new Set(multiselect.getSelectedUsernames());
save();
mw.notify('Changes will take effect in next load.', {
tag: 'rcmuter'
});
});
});
let togglesShown;
let $toggleToggles = $('<a>').attr({
href: '#',
role: 'button'
}).text('Show toggle buttons').on('click', function (e) {
e.preventDefault();
if (togglesShown) {
mw.hook('wikipage.content').remove(addToggles);
$('.rcmuter-toggle').remove();
this.textContent = 'Show toggle buttons';
} else {
mw.hook('wikipage.content').add(addToggles);
this.textContent = 'Hide toggle buttons';
}
togglesShown = !togglesShown;
});
let $toggleMuted = $('<a>').attr({
class: 'rcmuter-togglemuted',
href: '#',
role: 'button'
}).text('Show muted').on('click', function (e) {
e.preventDefault();
this.textContent = document.body.classList.toggle('rcmuter-disabled')
? 'Hide muted'
: 'Show muted';
});
let $toggleMutedSpan = $('<span>').hide().append($toggleMuted);
mw.util.addSubtitle(
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append($edit),
$('<span>').append($toggleToggles),
$toggleMutedSpan
)[0]
);
let toggle = function (e) {
e.preventDefault();
let user = $(this)
.closest('.mw-usertoollinks').closest(':not(:only-child)').prevAll()
.find('.mw-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');
muted[muting ? 'add' : 'delete'](user);
save();
this.textContent = muting ? 'unmute' : 'mute';
mw.notify(`${muting ? 'Muting' : 'Unmuting'} ${user} from next load.`, {
tag: 'rcmuter'
});
};
let addToggles = $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: '#',
role: 'button'
}).text('mute').on('click', toggle)
)
);
if (!$muted.length) return;
$muted.append(
$('<span>').addClass('rcmuter-toggle rcmuter-unmute').append(
$('<a>').attr({
href: '#',
role: 'button'
}).text('unmute').on('click', toggle)
)
);
};
let mutedCount;
let filter = function () {
let isMuted = muted.has(this.textContent);
if (isMuted) {
mutedCount++;
}
return isMuted;
};
mw.hook('wikipage.content').add($content => {
if (!$content.is('#mw-content-text, .mw-changeslist')) return;
if (!muted.size) {
$toggleMutedSpan.hide();
return;
}
mutedCount = 0;
$content.find('.changedby > .mw-changeslist-user-in-group:only-child .mw-userlink')
.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).closest('.mw-changeslist-user-in-group').addClass('rcmuter-muted');
$toggleMutedSpan.toggle(!!mutedCount);
$toggleMuted.attr('data-rcmuter', mutedCount);
});
});
location.hostname.endsWith('.wikipedia.org') &&
mw.config.get('wgNamespaceNumber') % 2 === 0 &&
// mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext' &&
$(async () => {
if (!document.getElementById('p-tb')) return;
await mw.loader.using('mediawiki.util');
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) {
window.refRenamer?.();
return;
}
clicked = true;
importScriptURI('//test.wikipedia.org/w/index.php?title=User:Nardog/sandbox6.js&action=raw&ctype=text/javascript');
mw.notify(messages.loading, {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
});
});
$('head > style').filter(function () {
return this.textContent.includes('.excon-mc');
}).remove();
$('.oo-ui-windowManager:has(.excon-mc)').remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
).then(() => {
let templates, css, dialog;
class EscapedString {
constructor(s) {
this.tokens = [{ s }];
this.multichoices = [];
}
escape(re, condition, callback) {
this.tokens = this.tokens.flatMap((token, i) => {
if (i % 2) {
return [token];
}
let newTokens = [], lastIndex = 0;
for (let match of token.s.matchAll(re)) {
if (match.index < lastIndex) continue;
if (condition && !condition(match)) continue;
newTokens.push({ s: token.s.slice(lastIndex, match.index) });
let newToken = { s: match[0] };
if (callback) {
callback(match, newToken, i);
}
newTokens.push(newToken);
lastIndex = match.index + newToken.s.length;
}
newTokens.push({ s: token.s.slice(lastIndex) });
return newTokens;
});
return this;
}
replace(...args) {
this.tokens.forEach((token, i) => {
if (i % 2) return;
token.s = token.s.replace(...args);
});
return this;
}
multichoice(re, ...options) {
return this.escape(re, null, (match, token, i) => {
let preLines = this.tokens.slice(0, i).map(({ s }) => s).join('')
.concat(match.input.slice(0, match.index))
.split('\n');
this.multichoices.push({
token,
lineNum: preLines.length,
pos: preLines.at(-1).length,
pre: preLines.at(-1),
post: match.input.slice(match.index + match[0].length)
.concat(this.tokens.slice(i + 1).map(({ s }) => s).join(''))
.match(/^[^\n]*/)[0],
options: [
...options.map(option => mw.format(option, ...match.slice(1))),
match[0]
]
});
});
}
makeForm() {
css = css || mw.loader.addStyleTag(`.excon-mc {
font-size: 90%;
}
.excon-mc:not(:first-child) {
margin-top: 10px;
}
.excon-mc-grid {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 6px;
margin-left: 12px;
}
.excon-mc-column {
overflow: auto;
}
.excon-mc input,
.excon-mc label {
white-space: pre;
display: flex;
align-items: center;
width: max-content;
height: 2em;
margin: 4px 0;
cursor: pointer;
}
.excon-mc-active {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}
.excon-mc b {
background-color: var(--background-color-content-added, #a3d3ff);
}
.excon-mc label:last-child b {
background-color: var(--background-color-content-removed, #ffe49c);
}`);
this.$form = $('<form>').on('change', function (e) {
$(e.target).closest('.excon-mc-grid').find('label')
.removeClass('excon-mc-active')
.filter(e.target.labels).addClass('excon-mc-active');
});
this.multichoices.sort((a, b) => (
a.lineNum - b.lineNum || a.pos - b.pos
)).forEach((multichoice, i) => {
$('<div>').addClass('excon-mc').text(
`Line ${multichoice.lineNum.toLocaleString('en')}:`
).append(
$('<div>').addClass('excon-mc-grid').append(
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<input>').attr({
type: 'radio',
name: `excon-${i}`,
value: j,
id: `excon-${i}-${j}`
}).prop('checked', !j)
))
),
$('<div>').addClass('excon-mc-column').append(
multichoice.options.map((option, j) => (
$('<label>').attr(
'for',
`excon-${i}-${j}`
).text(multichoice.pre).append(
$('<b>').text(option),
document.createTextNode(multichoice.post)
).toggleClass('excon-mc-active', !j)
))
)
)
).appendTo(this.$form);
});
return this.$form;
}
applyChoices() {
new FormData(this.$form[0]).forEach((v, n) => {
let multichoice = this.multichoices[n.slice(6)];
multichoice.token.s = multichoice.options[v];
});
}
unescape() {
return this.tokens.map(({ s }) => s).join('');
}
}
window.pathoschild.TemplateScript.add([{
name: 'Expand contractions',
script: async editor => {
if (!templates) {
templates = [
'Blockquote', 'Cquote', 'Quote frame', 'Rquote', 'Quote box',
'Poem', 'Poem quote', 'About', 'For', 'Other uses', 'Redirect',
'Distinguish', 'Main', 'Further', 'See also', 'Lang',
'Interlanguage link'
];
let prefix = mw.config.get('wgFormattedNamespaces')[10] + ':';
let params = {
action: 'query',
titles: templates.map(n => prefix + n),
redirects: 1,
prop: 'redirects',
rdprop: 'title',
rdnamespace: 10,
rdshow: '!fragment',
rdlimit: 'max',
formatversion: 2
};
templates = new Set(templates);
do {
try {
let response = await new mw.Api().get(params);
response.query?.pages?.forEach(page => {
[page, ...(page.redirects || [])].forEach(o => {
templates.add(o.title.slice(prefix.length));
});
});
params.rdcontinue = response.continue?.rdcontinue;
} catch {}
} while (params.rdcontinue);
}
let orig = editor.get();
let es = new EscapedString(orig);
console.log(es);
es
.escape(/<!--[^]+?-->/g)
.escape(
/\{\{[\s_]*([^{|}]+?)[\s_]*\|/g,
match => templates.has(
match[1][0].toUpperCase() + match[1].slice(1).replace(/[\s_]+/g, ' ')
),
(match, token) => {
let level = 0;
for (let submatch of match.input.slice(match.index + match[0].length).matchAll(/\{\{|\}\}/g)) {
if (submatch[0] === '{{') {
level++;
} else {
if (!level) {
token.s = match.input.slice(
match.index,
match.index + match[0].length + submatch.index + 2
);
break;
}
level--;
}
}
}
)
.escape(/<ref\s[^>]*\/>|<(blockquote|ref|poem)(?:\s?[^>]*)?>[^]+?<\/\1>|\[\[[^\[\]]+?\]\]|["“].+?["”]|(''+).+?\2|^===?\s*(?:References|Sources|Bibliography|Further reading|External links)\s*==[^]+|^=+.+=/gim)
.escape(/\|\s*(?:explain|filename|image|name|reason|song|title)\d*\s*=[^|}]+?(?=[|}])/gi)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:appeared|arisen|arrived|become|been|begun|behaved|belonged|come|consisted|departed|died|disappeared|elapsed|emerged|existed|expired|got|gotten|had|happened|lacked|lain|lasted|lingered|occurred|persisted|remained|resembled|resulted|seemed|stayed|stood|strive[dn]|struggled|thrived|vanished|waited)\b)/g,
'$1 has',
'$1 is'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Nn]o|[Ss]ome)thing)['’]s\b/g,
'$1 is',
'$1 has'
)
.replace(/\b([Cc])an['’]t\b/g, '$1annot')
.replace(/\b([Ww])on['’]t\b/g, '$1ill not')
.replace(/\b((?![Aa]i)[A-Z]?[a-z]+)n['’]t\b/g, '$1 not')
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d(?=,? (?:(?:not|n?ever|n?either|again|all|almost|already|also|always|earlier|even|far|forever|hence|however|indeed|instead|just|later|least|less|likewise|meanwhile|more|most|much|now|often|once|otherwise|perhaps|quite|rather|seldom|since|so|somehow|sometimes|soon|sooner|still|that|then|thereby|therefore|thus|today|together|too|twice|very|(?!(?:ally|anomaly|apply|assembly|belly|bully|butterfly|chilly|comply|costly|cuddly|curly|disassembly|family|firefly|folly|gainly|gnarly|gully|holy|hurly|imply|jelly|lily|lonely|measly|melancholy|monopoly|multiply|oily|panoply|rally|reassembly|rely|reply|sally|scholarly|silly|sully|supply|surly|tally|ugly|unholy|unruly) )[a-z]{2,}ly),? )*(?:agreed|arisen|awoken|beaten|been|begun|bent|bitten|blown|borne?|bought|broken|brought|built|burnt|caught|chosen|clung|crept|dealt|decreed|disagreed|done|drawn|dreamt|driven|drunk|dug|dwelt|eaten|fallen|fed|felt|flown|forbidden|foreseen|forgiven|forgot|forgotten|fought|found|freed|frozen|given|gone|got|gotten|grown|guaranteed|had|heard|held|hidden|hung|kept|known|laid|lain|leapt|learnt|led|left|lent|lit|lost|made|meant|met|misheard|mistaken|misunderstood|overheard|overseen|overtaken|paid|partaken|proven|rebuilt|rewound|rewritten|ridden|risen|rung|said|sank|sat|seen|sent|sewn|shaken|shone|shot|shown|shrunk|slain|slept|slid|snuck|sold|sought|sown|spat|spent|spilt|spoken|sprung|spun|stolen|stood|stricken|struck|strung|stuck|stung|sung|sunk|swept|sworn|swum|swung|taken|taught|thought|thrown|told|torn|undergone|understood|undone|unwound|upheld|withdrawn|withheld|woken|won|worn|woven|written|wrought|(?![^aeiou]+ed\b|embed\b)[a-z]+[a-df-z]ed)\b)/g,
'$1 had',
'$1 would'
)
.multichoice(
/\b([Hh]e|[Ss]he|[Ii]t|[Tt]hey|[Tt]hat|[Tt]here|[Ww]hat(?:ever)?|[Ww]ho(?:ever)?|[Ww]here(?:ver)?|(?:[Aa]ny|[Ee]very|[Ss]ome)(?:body|one|thing)|[Nn]o(?:body|[ -]one|thing))['’]d\b/g,
'$1 would',
'$1 had'
)
.replace(/([a-z])['’]re\b/g, '$1 are')
.replace(/([a-z])['’]ve\b/g, '$1 have')
.replace(/([a-z])['’]ll\b/g, '$1 will');
if (es.multichoices.length) {
if (!dialog) {
await mw.loader.using('oojs-ui-windows');
dialog = new OO.ui.MessageDialog();
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
window.exconDialog = dialog;
}
dialog.title.$element.nextAll().remove().end().after(es.makeForm());
let instance = dialog.open({
size: 'larger',
title: 'Ambiguous contractions found'
});
instance.opened.then(() => {
setTimeout(() => {
es.$form.find('.excon-mc b').each(function () {
this.scrollIntoView({
behavior: 'instant',
container: 'nearest',
inline: 'center'
});
});
es.$form.closest('.oo-ui-panelLayout-scrollable')
.prop('scrollTop', 0);
}, 500);
});
let result = await instance.closed;
if (result?.action !== 'accept') {
mw.notify('Expansion canceled.');
return;
}
es.applyChoices();
}
let repl = es.unescape();
if (repl === orig) {
mw.notify('No change.');
return;
}
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
editor
.set(repl)
.appendEditSummary(`expanded contractions using [[${iw}User:Nardog/ExpandContractions|script]]`)
.options({ minor: true })
.clickDiff();
}
}]);
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when(
$.ajax(
'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js',
{ dataType: 'script', cache: true }
),
mw.loader.using(['mediawiki.api', 'mediawiki.Title'])
).then(() => {
window.pathoschild.TemplateScript.add([{
name: 'Unpipe links',
script: async editor => {
let orig = editor.get();
let repl = orig, links = {}, titles = new Set(), redirs = {};
for (let [code, target, text, trail] of orig.matchAll(/\[\[([^|\]]+)\|([^\]]+)\]\]([a-z]*)/g)) {
if (Object.hasOwn(links, code)) continue;
let strings = [target, text];
if (trail) {
strings.push(text + trail);
}
let mwTitles = strings.map(s => mw.Title.newFromText(s));
if (mwTitles.some(t => !t || t.getNamespaceId())) continue;
let sanitizedPns = mwTitles.map(t => t.toText());
if (sanitizedPns[0].endsWith(' (disambiguation)')) continue;
if (sanitizedPns[0] === sanitizedPns[1] && (
mwTitles[0].getFragment() === mwTitles[1].getFragment()
)) {
repl = repl.replaceAll(code, `[[${text}]]`);
continue;
}
links[code] = { strings, mwTitles, sanitizedPns };
titles.add(...sanitizedPns);
}
titles = [...titles];
while (titles.length) {
let { query } = await new mw.Api().post({
action: 'query',
titles: titles.slice(0, 50),
redirects: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.redirects.forEach(r => {
redirs[r.from] = r;
});
titles = titles.slice(50);
}
Object.entries(links).forEach(([code, obj]) => {
// if (!obj.sanitizedPns.some(pn => Object.hasOwn(redirs, pn))) return;
let results = obj.sanitizedPns.map((pn, i) => (
[
redirs[pn]?.to || pn,
obj.mwTitles[i].getFragment() || redirs[pn]?.tofragment
].join('#')
));
if (results[1] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1]}]]${obj.strings[2] || ''}`);
} else if (results[2] && results[2] === results[0]) {
repl = repl.replaceAll(code, `[[${obj.strings[1] + obj.strings[2]}]]`);
}
});
if (repl === orig) {
mw.notify('No change.');
return;
}
editor
.set(repl)
.appendEditSummary('[[WP:NOPIPE|unpiped]] links using [[User:Nardog/Unpipe|script]]')
.options({ minor: true })
.clickDiff();
}
}]);
});
mw.config.get('wgAction') !== 'history' &&
isc('User:Nardog/CopyCodeBlock.js', 'en.wikipedia.org');
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({ behavior: 'smooth' });
} else {
yToSpan[Math.min(...lower)].scrollIntoView({ behavior: 'smooth' });
}
};
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({ behavior: 'smooth' });
};
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);
}).append(
' | ',
$('<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;
});
await mw.loader.using('mediawiki.api');
let response = await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}/html`
);
$($.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', async e => {
e.preventDefault();
await new mw.Api().post({
action: 'purge',
forcelinkupdate: 1,
titles: config.page.name,
formatversion: 2
});
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.startsWith('!');
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', async function (e) {
if (e.which > 2) return;
e.preventDefault();
let revId = mw.config.get('wgRevisionId') || Number($('input[name="oldid"]').val());
let path = revId
? '/v1/revision/' + revId
: '/v1/page/' + config.page.encodedName;
let { source } = await new mw.Rest().get(path);
$('<form>').attr({
method: 'post',
action: this.href,
target: '_blank'
}).append(
[
['wpInput', 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' &&
mw.loader.using('mediawiki.storage', async () => {
let infuseAndCall = (query, method, ...args) => {
let $widget = $(query);
if ($widget.length) {
return OO.ui.infuse($widget)[method](...args);
}
};
await $.ready;
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 sections = (await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'tocdata',
wrapoutputclass: '',
disablelimitreport: 1,
disableeditsection: 1,
disablestylededuplication: 1,
disabletoc: 1,
formatversion: 2
})).parse.tocdata.sections;
let target = !isLead && sections.find(s => s.index === data[1]);
if (isLead || target) {
let next = sections.find(s => s.index - 1 === Number(data[1]));
newSource = (isLead ? '' : [...source].slice(0, target.codepointOffset)).join('') +
data[2] + (next ? '\n\n' + [...source].slice(next.codepointOffset).join('') : '\n');
start = isLead ? 0 : target.codepointOffset;
} 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')) &&
importScriptURI('//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.startsWith('W') ? titles.w[id.slice(1) - 1] : titles.fa[id.slice(2) - 1]
);
let renderTitle = (title, id, tag = 'span') => {
let classes = 'wishtitle';
if (id.startsWith('W') && titles.wd?.includes(id.slice(1) - 1)) {
classes += ' wishtitle-declined';
}
return $(`<${tag}>`).addClass(classes).append(
$('<a>').attr({
href: `/wiki/Community_Wishlist/${id}`,
title: `Community Wishlist/${id}`
}).text(title)
);
};
let callback = ([id, links]) => {
let title = getTitle(id);
if (!title) {
return true;
}
$(links).after(' ', renderTitle(title, id));
};
let selector = '.mw-changeslist-title, ' +
'.mw-changeslist-log-entry > a:not(.mw-userlink), ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize :is(.mw-changeslist-line-inner, .mw-changeslist-line-inner-comment, .mw-enhanced-rc-nested) > .comment > a, ' +
'#watchlist-edit-form .cdx-table td > label > a, ' +
'.mw-search-result-heading > a:not(:has(> .ext-communityrequests-entity-link--label)), ' +
'.mw-contributions-title, ' +
'#mw-whatlinkshere-list li > bdi > a, ' +
'.mw-allpages-chunk > li > a, ' +
'.mw-prefixindex-list > li > a, ' +
'.mw-logevent-loglines > li > a, ' +
'#mw-pages li > a, ' +
'.catchangesviewer-table td:nth-child(3) > a';
mw.hook('wikipage.content').add(async $content => {
let links = {};
$content.find('a').each(function () {
if (!this.matches(selector)) return;
let id = this.textContent.match(
/^(?:Talk:|Translations:)?Community Wishlist\/((?:W|FA)\d+)/
)?.[1];
if (!id) return;
(links[id] = links[id] || []).push(this);
});
links = Object.entries(links);
if (!links.length) return;
await loadTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles();
links = links.filter(callback);
if (!links.length) return;
await updateTitles('declined');
links.forEach(callback);
});
let pn = mw.config.get('wgRelevantPageName');
let id = pn.match(/^(?:Talk:|Translations:)?Community_Wishlist\/((?:W|FA)\d+)/)?.[1];
if (!id) return;
await $.ready;
let extTitle = document.querySelector('.ext-communityrequests-wish--title');
if (extTitle && $('.mw-pt-languages-selected').attr('lang') === lang) return;
await loadTitles();
let title = getTitle(id);
if (!title) {
await updateTitles();
title = getTitle(id);
if (!title) {
await updateTitles('declined');
title = getTitle(id);
if (!title) return;
}
}
let $title = renderTitle(title, id, 'div');
if (mw.config.get('skin') === 'vector-2022') {
$title.prependTo('.vector-page-toolbar');
} else {
$title.insertAfter('#firstHeading');
}
css.textContent += ' .ext-communityrequests-entity-talk-header{display:none}';
if (extTitle) return;
document.title = document.title.replace(
pn.replaceAll('_', ' '),
`${pn.replace(`Community_Wishlist/${id}`, title)} ($&)`
);
})();
mw.config.get('wgWikiID') === 'metawiki' &&
mw.hook('wikipage.watchlistChange').add(async (isWatched, expiry) => {
if (![0, 1].includes(mw.config.get('wgNamespaceNumber'))) return;
let title = mw.config.get('wgTitle');
if (!/^Community Wishlist\/(?:W|FA)\d+$/.test(title)) return;
if (isWatched) {
await new mw.Api().watch(title + '/Votes', expiry);
mw.notify('Watching /Votes too.');
} else {
await new mw.Api().unwatch(title + '/Votes');
mw.notify('Unwatched /Votes too.');
}
});
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async () => {
let $input = $('#wpTemplateSandboxTemplate');
if (!$input.length) return;
mw.loader.addStyleTag('#templatesandbox-editform .oo-ui-fieldLayout{max-width:50em} #templatesandbox-editform .oo-ui-fieldLayout-field{flex-grow:999}');
let makeTemplateField = () => new OO.ui.FieldLayout(
new mw.widgets.TitleInputWidget({
inputId: 'wpTemplateSandboxTemplate',
name: 'wpTemplateSandboxTemplate',
showMissing: false,
value: $input.val()
}),
{ label: 'Template name:' }
);
if (mw.loader.getState('ext.TemplateSandbox') !== 'registered') {
await mw.loader.using('mediawiki.widgets');
$input.parent().replaceWith(makeTemplateField().$element);
return;
}
let require = await mw.loader.using([
'ext.TemplateSandbox.TemplateSandboxTitleWidget',
'ext.TemplateSandbox.styles', 'jquery.makeCollapsible', 'user.options'
]);
let widget = new (require('ext.TemplateSandbox.TemplateSandboxTitleWidget'))({
$overlay: true,
id: 'wpTemplateSandboxPage',
maxLength: 255,
name: 'wpTemplateSandboxPage',
placeholder: 'Page title',
required: false,
tabIndex: 10,
templateTitleFunc: () => $('#wpTemplateSandboxTemplate').val()
});
widget.$element.attr('data-ooui', '{"_":"mw.widgets.TemplateSandboxTitleWidget"}')
.data('oouiInfused', widget);
let fieldset = new OO.ui.FieldsetLayout({
classes: ['mw-templatesandbox-fieldset', 'mw-collapsed'],
id: 'templatesandbox-editform',
items: [
makeTemplateField(),
new OO.ui.ActionFieldLayout(
widget,
new OO.ui.ButtonInputWidget({
id: 'wpTemplateSandboxPreview',
name: 'wpTemplateSandboxPreview',
label: 'Show preview',
tabIndex: 10,
type: 'submit',
useInputTag: true
}),
{ align: 'top' }
)
],
label: 'Preview page with this template'
});
fieldset.$label.append(' ', $('<span>').addClass('mw-collapsible-toggle-placeholder'));
fieldset.$group.addClass('mw-collapsible-content');
$('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible());
let modules = ['ext.TemplateSandbox'];
if (Number(mw.user.options.get('uselivepreview'))) {
modules.push('ext.TemplateSandbox.preview');
}
mw.loader.load(modules);
});
mw.config.get('wgWikiID') === 'enwiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
(async () => {
mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}');
await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']);
let xfds = [
{
id: 'rm',
label: 'RM',
full: 'Requested moves',
cat: 'Requested moves',
},
{
id: 'rmt',
label: 'RM/T',
full: 'Requested moves (technical)',
page: 'Wikipedia:Requested_moves/Technical_requests',
titleExtractor: $page => (
$page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () {
return this.querySelector('a[rel="mw:WikiLink"]')?.title;
}).get()
)
},
{
id: 'afd',
label: 'AfD',
full: 'Articles for deletion',
cat: 'Articles for deletion'
},
{
id: 'mfd',
label: 'MfD',
full: 'Miscellaneous for deletion',
cat: 'Miscellaneous pages for deletion'
},
{
id: 'tfd',
label: 'TfD',
full: 'Templates for deletion',
cat: 'Templates for deletion'
},
{
id: 'tfm',
label: 'TfM',
full: 'Templates for merging',
cat: 'Templates for merging'
},
{
id: 'cfd',
label: 'CfD',
full: 'Categories for deletion',
cat: 'Categories for deletion'
},
{
id: 'cfr',
label: 'CfR',
full: 'Categories for renaming',
cat: 'Categories for renaming'
},
{
id: 'cfsr',
label: 'CfSR',
full: 'Categories for speedy renaming',
cat: 'Categories for speedy renaming'
},
{
id: 'cfm',
label: 'CfM',
full: 'Categories for merging',
cat: 'Categories for merging'
},
{
id: 'cfs',
label: 'CfS',
full: 'Categories for splitting',
cat: 'Categories for splitting'
},
{
id: 'cfl',
label: 'CfL',
full: 'Categories for listifying',
cat: 'Categories for listifying'
},
{
id: 'cfc',
label: 'CfC',
full: 'Categories for conversion',
cat: 'Categories for conversion'
},
{
id: 'cfgd',
label: 'CfGD',
full: 'Categories for general discussion',
cat: 'Categories for general discussion'
},
{
id: 'ffd',
label: 'FfD',
full: 'Files for discussion',
cat: 'Wikipedia files for discussion'
},
{
id: 'rfd',
label: 'RfD',
full: 'Redirects for discussion',
cat: 'All redirects for discussion'
},
{
id: 'prod',
label: 'PROD',
full: 'Articles proposed for deletion',
cat: 'All articles proposed for deletion'
}
];
window.xfd = xfds;
let queryTitles = async (xfd, titles) => {
if (!titles.length) return;
let response = await new mw.Api().get({
action: 'query',
titles: titles.slice(0, 50),
prop: 'info',
inprop: 'watched',
formatversion: 2
});
response?.query?.pages?.forEach(p => {
if (p.watched) {
xfd.pages.push(p.title);
}
});
await queryTitles(xfd, titles.slice(50));
};
let queryPage = async xfd => {
let $page = $($.parseHTML(await new mw.Rest().get(
`/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));
})();
pj9pinzwdqewsfrubajnwtldibe2pag
Nicolás Mazzarino
0
122123
769848
727548
2026-10-23T19:41:44Z
InternetArchiveBot
34092
Rescuing 2 sources and tagging 0 as dead.) #IABot (v2.0.9.6
769848
wikitext
text/x-wiki
{{Infobox basketball biography
| name = Nicolás Mazzarino
| image =
| image_size =
| caption =
| position = [[Point guard]] / [[Shooting guard]]
| height_ft = 6
| height_in = 0
| weight_lbs = 195
| league = [[Uruguayan Basketball League|Uruguayan League]]
| team = [[Club Malvín|Malvín]]
| number = 12
| nationality = [[Uruguayan]] / [[Italians|Italian]]
| birth_date = {{birth date and age|1975|10|21}}
| birth_place = [[Salto, Uruguay]]
| high_school =
| college =
| draft =
| draft_year =
| career_start = 1991
| career_end =
| years1 = 1991–1996
| team1 = [[Hebraica y Macabi]]
| years2 = 1997–2001
| team2 = [[Club Atlético Welcome|Welcome]]
| years3 = 2001
| team3 = [[Boca Juniors (basketball)|Boca Juniors]]
| years4 = 2001–2002
| team4 = [[Club Atlético Welcome|Welcome]]
| years5 = 2002–2005
| team5 = [[Viola Reggio Calabria]]
| years6 = 2005–2013
| team6 = [[Pallacanestro Cantù|Mapooro Cantù]]
| years7 = 2013–present
| team7 = [[Club Malvín|Malvín]]
| highlights =
*[[Italian Basketball Supercup|Italian Supercup]] winner (2012)
*[[Lega Basket All Star Game#Three-Point Contest|Italian All Star Game]] 3 Point Shootout Champion (2011)
*[[FIBA South American Championship]] Top Scorer (2004)
*8× [[List of Uruguayan basketball champions|Uruguayan League]] champion (1994, 1997, 1998, 1999, 2000, 2014, 2015, 2018)
*[[Uruguayan Basketball League Finals MVP|Uruguayan League Finals MVP]] (2018)
| medal_templates =
{{MedalSport|Men's [[Basketball]]}}
{{MedalCountry | {{bk|URU}}}}
{{MedalCompetition|[[Basketball at the Pan American Games|Pan American Games]]}}
{{MedalBronze|[[Basketball at the 2007 Pan American Games|2007 Rio de Janeiro]] | }}
{{MedalCompetition|[[FIBA South American Championship]]}}
{{MedalGold|[[South American Basketball Championship|1997 Maracaibo]] | }}
{{MedalSilver|[[South American Basketball Championship 2008|2008 Puerto Montt]] | }}
}}
[[File:Demartini e Mazzarino.jpg|thumb|Testing123]]
'''Nicolás Mazzarino''' (born October 21, 1975, in [[Salto, Uruguay|Salto]]) is a [[Uruguay]]an professional [[basketball]] player for [[Club Malvin]] in the [[Liga Uruguaya de Básquetbol]]. He also holds an Italian passport.<ref>[https://basketball.latinbasket.com/player/Puerto_Rico/Capitanes_de_Arecibo/Leandro_Garcia_Morales/39048 NICOLAS MAZZARINO basketball profile Nationality: Italian-Uruguayan.]</ref> At a height of 1.83 m (6'0") tall,<ref>{{Cite web |url=http://archive.fiba.com/pages/eng/fa/player/p/pid/40981/sid/13274/tid//_/2017_Liga_Sudamericana_de_Clubes_FIBA/index.html |title=Nicolas MAZZARINO (ITA) Height: 183cm/6'0". |archive-url=https://web.archive.org/web/20181128075603/http://archive.fiba.com/pages/eng/fa/player/p/pid/40981/sid/13274/tid//_/2017_Liga_Sudamericana_de_Clubes_FIBA/index.html |archive-date=2018-11-28 |access-date=2021-01-23 |url-status=dead}}</ref> he plays at the [[point guard]] and [[shooting guard]] positions.
==Professional career==
Mazzarino began his professional career in the Uruguayan club [[Hebraica y Macabi]] in 1991. In this club, Mazzarino won the [[Uruguayan Federal Basketball Championship|Uruguayan Federal Championship]] title in 1994. In 1997, he moved to [[Club Atlético Welcome|C.A. Welcome]], another Uruguayan [[basketball]] team, where he won 4 consecutive Uruguayan Federal Championships. In 2001, Mazzarino played a brief time in the [[Liga Nacional de Básquet|Argentinian League]] team [[Boca Juniors (basketball)|C.A. Boca Juniors]], but then he returned to C.A. Welcome.
In 2001, he moved to the [[Lega Basket Serie A|Italian League]], to play in [[Viola Reggio Calabria]], where he became the team's [[captain (sports)|captain]] and best scorer in the [[Serie A (basketball) 2004–05|2004–05 season]].
After that season, he moved to [[Pallacanestro Cantù]]. In the [[Serie A (basketball) 2007–08|2007–08 season]], he became the captain of Cantù. His best season with Cantù was the [[Lega Basket Serie A 2009–10|2009–10 season]], when he helped Cantù make it to the Italian League semifinals, after averaging 13.8 points and 2.8 assists per game.
In 2013, he returned to Uruguay, and began playing at [[Club Malvín]]. With the team, he won the [[Uruguayan Basketball League]] championship in 2014, 2015, and 2018.
==National team career==
Mazzarino played with the senior [[Uruguayan national basketball team|Uruguayan national team]] from 1997 to 2014. He played with Uruguay at 6 [[FIBA South American Championship]]s (1997, 1999, 2001, 2004, 2008, 2014), 6 [[FIBA AmeriCup]]s ([[1997 Tournament of the Americas|1997]], [[1999 Tournament of the Americas|1999]], [[2001 Tournament of the Americas|2001]], [[2003 Tournament of the Americas|2003]], [[FIBA Americas Championship 2005|2005]], [[FIBA Americas Championship 2007|2007]], [[2013 FIBA AmeriCup|2013]]), and 3 [[Basketball at the Pan American Games|Pan American Games]] ([[Basketball at the 1999 Pan American Games|1999]], [[Basketball at the 2003 Pan American Games|2003]], and [[Basketball at the 2007 Pan American Games|2007]]).
==References==
{{reflist}}
==External links==
*[http://www.euroleague.net/competition/players/showplayer?pcode=CQJ&clubcode=CTU#!careerstats Euroleague.net Profile]
*[https://web.archive.org/web/20110808100642/http://archive.fiba.com/pages/eng/fa/p/q/Nicolas%20MAZZARINO/pid/40981/_/players.html FIBA Profile]
*[http://www.fibaeurope.com/cid_KNce8jInH7Qj1EsyH5rjn2.teamID_96465.compID_BYg5Rb55Jw-G5I3MZ6JB01.season_2006.roundID_4598.playerID_40981.html FIBA Europe Profile]
*[https://basketball.latinbasket.com/player/Nicolas-Mazzarino/Uruguay/Malvin-Montevideo/1712&AmNotSure=1 Latinbasket.com Profile] {{Webarchive|url=https://web.archive.org/web/20181128075523/https://basketball.latinbasket.com/player/Nicolas-Mazzarino/Uruguay/Malvin-Montevideo/1712%26AmNotSure%3D1 |date=2018-11-28 }}
*[http://www.draftexpress.com/profile/Nicolas-Mazzarino-5929/ Draftexpress.com Profile] {{Webarchive|url=https://web.archive.org/web/20190728110955/http://www.draftexpress.com/profile/Nicolas-Mazzarino-5929/ |date=2019-07-28 }}
*[http://195.56.77.208/player/?id=MAZ-NIC Italian League Profile] {{Webarchive|url=https://web.archive.org/web/20130628071055/http://195.56.77.208/player/?id=MAZ-NIC |date=2013-06-28 }} {{in lang|it}}
{{Uruguay Men Basketball Squad 1999 Pan American Games}}
{{Liga Uruguaya de Básquetbol Finals MVP}}
{{DEFAULTSORT:Mazzarino, Nicolas}}
[[Category:1975 births]]
[[Category:Living people]]
[[Category:Basketball players at the 1999 Pan American Games]]
[[Category:Basketball players at the 2003 Pan American Games]]
[[Category:Basketball players at the 2007 Pan American Games]]
[[Category:Boca Juniors basketball players]]
[[Category:Club Atlético Welcome basketball players]]
[[Category:Club Malvín basketball players]]
[[Category:Expatriate basketball people in Argentina]]
[[Category:Italian men's basketball players]]
[[Category:Pallacanestro Cantù players]]
[[Category:Pan American Games bronze medalists for Uruguay]]
[[Category:Pan American Games medalists in basketball]]
[[Category:Sportspeople from Salto, Uruguay]]
[[Category:Point guards]]
[[Category:Shooting guards]]
[[Category:Uruguayan men's basketball players]]
[[Category:Uruguayan people of Italian descent]]
[[Category:Viola Reggio Calabria players]]
[[Category:Medalists at the 2007 Pan American Games]]
qtlgymd5h8ok9hlt82cf0afeaef9cme
October 1935
0
122228
769849
727892
2026-10-23T19:52:06Z
InternetArchiveBot
34092
Rescuing 1 sources and tagging 0 as dead.) #IABot (v2.0.9.6
769849
wikitext
text/x-wiki
{{short description|Month of 1935}}
{{Events by month|1935}}
{{calendar|year=1935|month=October}}
The following events occurred in '''October 1935''':
==[[October 1]], 1935 (Tuesday)==
*A new law went into effect in [[Thailand|Siam]] that no longer recognized [[Polygamy in Thailand|polygamy]].<ref>{{cite web |url=http://etheses.lse.ac.uk/2315/1/U615262.pdf |title=New Economy and Gender Relations in Thailand |last=Komolvadhin |first=Nattha |date=2014 |website=London School of Economics Theses Online |publisher=ProQuest LLC |access-date=July 24, 2015 |archive-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150724114823/http://etheses.lse.ac.uk/2315/1/U615262.pdf |url-status=dead }}</ref>
*Sport became mandatory in German schools.<ref name="chronicle of the 20th c.">{{cite book |last=Mercer |first=Derrik |date=1989 |title=Chronicle of the 20th Century |location=London |publisher=Chronicle Communications Ltd. |pages=457, 459 |isbn=978-0-582-03919-3 }}</ref>
*The [[Boston Housing Authority]] was formed.
*'''Born:''' [[Julie Andrews]], actress and singer, in [[Walton-on-Thames]], England; [[Julio Jaramillo]], singer, in [[Guayaquil]], [[Ecuador]] (d. 1978); [[Teruyoshi Nakano]], special effects director, in [[Andong Province]], [[Manchukuo]]
==[[October 2]], 1935 (Wednesday)==
*Unconfirmed reports circulated of a battle between Italians and Ethiopians in the vicinity of [[Mousa Ali]]. Italian officials denied the reports.<ref name="tribune oct 3">{{cite news |date=October 3, 1935 |title=Ethiopia Charges Air Raid | work=[[Chicago Daily Tribune]]|page=1 }}</ref>
*[[Benito Mussolini]] gave a radio address in Rome informing Italians that "A solemn hour is about to sound in the history of the fatherland ... For many months the wheels of destiny have been moving toward their goal under the impulse of our calm determination. In the latter hours their rhythm has become more swift and by now cannot be stopped. It is not only an army that strives towards its objectives but a whole people of 44 million souls against whom an attempt is being made to consumnate the blackest of injustices – that of depriving us of some small place in the sun."<ref>{{cite news |date=October 2, 1935 |title=Rome Mobilizes Entire Nation to Hear Speech | work=[[Brooklyn Daily Eagle]] |pages=1–2 }}</ref><ref>{{cite web |url=http://users.dickinson.edu/~rhyne/232/EthiopiaSpeech.html |title=Mussolini Justifies War Against Ethiopia |website=[[Dickinson College]] |access-date=July 24, 2015 |archive-date=November 9, 2015 |archive-url=https://web.archive.org/web/20151109153132/http://users.dickinson.edu/~rhyne/232/EthiopiaSpeech.html |url-status=dead }}</ref>
*[[Bulgaria]] imposed a state of emergency after a failed coup against the government.<ref>{{cite web |url=http://www.chroniknet.de/daly_de.0.html?datum=2.10.1935&year=1935&month=10&day=2 |title=Tageseinträge für 2. Oktober 1935 |website=chroniknet |access-date=July 24, 2015 |archive-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150724122009/http://www.chroniknet.de/daly_de.0.html?datum=2.10.1935&year=1935&month=10&day=2 |url-status=dead }}</ref>
*'''Born:''' [[Robert Henry Lawrence, Jr.]], astronaut, in [[Chicago]], [[Illinois]] (d. 1967)
==[[October 3]], 1935 (Thursday)==
*[[League of Nations]] officials announced that they had received a communication from Ethiopia asserting that [[Adwa]] had been bombed by Italian warplanes.<ref name="tribune oct 3" /> Emperor [[Haile Selassie]] informed a [[Reuters]] correspondent: "I have just received the news that the first bombs dropped by Italian planes on Adwa fell on the [[International Red Cross and Red Crescent Movement|Red Cross]] Hospital there, killing and wounding nurses."<ref>{{cite news |date=October 3, 1935 |title=France and Britain Plan Finance Boycott of Italy | work=[[Brooklyn Daily Eagle]] |page=1 }}</ref> The [[Second Italo-Ethiopian War]] had begun.
*Haile Selassie ordered a general mobilization.<ref name="tribune oct 3" />
*The first phase of the war began with [[De Bono's invasion of Abyssinia]] in the north.<ref>{{cite news |date=October 4, 1935 |title=Italy Hits With 4 Armies | work=[[Chicago Daily Tribune]]|page=1 }}</ref>
*[[Nazi Germany]] declared neutrality in the Ethiopian conflict.<ref>{{cite news |last=Schultz |first=Sigrid |author-link=Sigrid Schultz |date=October 4, 1935 |title=Germany Adopts 'Hands Off' Rule for Italian War | work=[[Chicago Daily Tribune]]|page=2 }}</ref>
*'''Born:''' [[Charles Duke]], American astronaut, U.S. Air Force pilot, and test pilot, in [[Charlotte, North Carolina]]; [[Armen Dzhigarkhanyan]], Soviet Russian-Armenian actor, in [[Yerevan]], Armenian SSR (d. 2020); [[Sinikka Kurkinen]], painter, in [[Joutseno]], Finland
==[[October 4]], 1935 (Friday)==
*France informed Britain that it would support the enforcement of sanctions against Italy and pledged military support in the event of any attack that arose from them.<ref>{{cite news |date=October 5, 1935 |title=Laval Gets Free Hand on How to Deal with War | work=[[Chicago Daily Tribune]]|page=1 }}</ref>
*Italian forces captured [[Adigrat]] and [[Enticho]].<ref name="nicolle">{{cite book |last=Nicolle |first=David |date=2005 |title=The Italian Invasion of Abyssinia 1935–36 |publisher=[[Osprey Publishing]] |page=8 |isbn=978-1-85532-692-7 }}</ref>
*The Italian delegation at the League of Nations maintained that Italy was not waging war, but was only engaged in "military police measures to establish order."<ref>{{cite news |date=October 5, 1935 |title=It's Not War, Italy Insists | work=[[Chicago Daily Tribune]]|page=1 }}</ref>
*[[Luna Park Sydney|Luna Park]] opened in [[Sydney]], Australia.
*'''Died:''' [[Jean Béraud]], 86, French painter
==[[October 5]], 1935 (Saturday)==
*U.S. President [[Franklin D. Roosevelt]] invoked the August 31 [[Neutrality Acts of 1930s|Neutrality Act]] to place an arms [[embargo]] on both Italy and Ethiopia.<ref>{{cite web |url=http://www.chroniknet.de/daly_de.0.html?datum=5.10.1935&year=1935&month=10&day=5 |title=Tageseinträge für 5. Oktober 1935 |website=chroniknet |access-date=July 24, 2015 |archive-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150724115933/http://www.chroniknet.de/daly_de.0.html?datum=5.10.1935&year=1935&month=10&day=5 |url-status=dead }}</ref>
*[[Collingwood Football Club|Collingwood]] defeated [[Sydney Swans|South Melbourne]] in the 39th [[1935 VFL Grand Final|Victorian Football League Grand Final]].
==[[October 6]], 1935 (Sunday)==
*Italian forces captured [[Adwa]].<ref name="nicolle" />
*Haile Selassie made another public statement to the world through the [[Associated Press]], saying, "Mr. Mussolini charges us with being barbarians and says he wishes to civilize us. Is the wanton slaughter of women and children by air bombs and machine guns the kind of civilization he wishes to give us? ... Despite the fact that our empire is faced with the gravest crisis of its long and glorious history – a crisis with which we have always striven to live in peace and amity – we still place all our faith in the League of Nations, which is pledged to defend its members, the small as well as the great, from unjustifiable aggression."<ref>{{cite news |date=October 7, 1935 |title=Emperor of Ethiopia Scores Italy's Troops as 'Slaughterers' | work=[[Chicago Daily Tribune]]|page=2 }}</ref>
*Communists in [[Mexico City]] tore the swastika flag from Germany's honorary consulate and dragged it through the streets.<ref>{{cite news |date=October 7, 1935 |title=Mexican Reds Drag Nazi Flag Through Streets | work=[[Chicago Daily Tribune]]|page=5 }}</ref>
*'''Born:''' [[Bruno Sammartino]], professional wrestler, in [[Abruzzo]], Italy (d. 2018); [[Charito Solis]], film actress, in [[Tondo, Manila]], [[Philippines]] (d. 1998)
==[[October 7]], 1935 (Monday)==
*By unanimous vote, the League of Nations declared Italy guilty of committing an act of war against all members of the organization by invading Abyssinia. The vote laid the basis for [[economic sanctions]].<ref>{{cite news |date=October 7, 1935 |title=Council Votes Italy Guilty; Sanction Action Next Step | work=[[Brooklyn Daily Eagle]] |page=1 }}</ref>
*The [[Detroit Tigers]] won the [[1935 World Series|World Series]] with a 4–3 victory over the [[Chicago Cubs]] in Game 6.
*The French dockworker's union called on its members to boycott the loading and unloading of Italian ships.<ref>{{cite web |url=http://www.chroniknet.de/daly_de.0.html?datum=7.10.1935&year=1935&month=10&day=7 |title=Tageseinträge für 7. Oktober 1935 |website=chroniknet |access-date=July 24, 2015 |archive-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150724120459/http://www.chroniknet.de/daly_de.0.html?datum=7.10.1935&year=1935&month=10&day=7 |url-status=dead }}</ref>
*The [[United States Supreme Court]] moved to the new $10 million [[United States Supreme Court Building|Supreme Court Building]].<ref>{{cite news |date=October 7, 1935 |title=U.S. Supreme Court Will Move Today to New 10 Million Home | work=[[Chicago Daily Tribune]]|page=4 }}</ref>
*Five people were arrested in Los Angeles as suspects in a plot to extort $1,000 from [[Mae West]] by sending letters threatening to kill her or disfigure her by throwing acid in her face.<ref>{{cite news |date=October 8, 1935 |title=Threaten Acid for Mae West; 5 Held in Plot | work=[[Chicago Daily Tribune]]|page=1 }}</ref>
*'''Died:''' [[Francis Wilson (actor)|Francis Wilson]], 81, American actor
==[[October 8]], 1935 (Tuesday)==
*Mussolini forbade anyone from bringing more than 2,000 lire into Italy at a time. Tourists had been changing their money for Italian currency in adjoining countries at a considerable discount.<ref>{{cite news |date=October 9, 1935 |title=Mussolini Tightens Regulation Against the "Black Bourse" | work=[[Chicago Daily Tribune]]|page=5 }}</ref>
*[[Clement Attlee]] was appointed the interim leader of the [[Labour Party (UK)|Labour Party]].<ref name="chronicle of the 20th c." />
==[[October 9]], 1935 (Wednesday)==
*Austria and Hungary announced that they would not co-operate with the League of Nations in applying any sanctions against Italy.<ref>{{cite news |date=October 9, 1935 |title=Austria, Hungary Bar Sanctions; Refuse to Aid League Against Italy | work=[[Brooklyn Daily Eagle]] |page=1 }}</ref>
*The film ''[[A Midsummer Night's Dream (1935 film)|A Midsummer Night's Dream]]'', adapted from the [[William Shakespeare]] [[A Midsummer Night's Dream|play of the same name]], premiered at the [[Mark Hellinger Theatre|Hollywood Theatre]] in New York.<ref>{{cite web |url=http://www.tcm.com/tcmdb/title/1806/A-Midsummer-Night-s-Dream/ |title=A Midsummer Night's Dream |website=[[Turner Classic Movies]] |access-date=July 24, 2015 }}</ref><ref>{{cite web |url=https://www.nytimes.com/movie/review?res=9E03E5D8103CE731A25753C1A9669D946494D6CF |title=Movie Review |last=Sennwald |first=Andre |date=October 10, 1935 |website=[[The New York Times]] |access-date=July 24, 2015 }}</ref>
*'''Born:''' [[Prince Edward, Duke of Kent]], in [[Belgrave Square]], London, England
==[[October 10]], 1935 (Thursday)==
*Greek President [[Alexandros Zaimis]] was deposed in a military coup. [[Panagis Tsaldaris]] was forced to resign and was succeeded by [[Georgios Kondylis]].<ref name="musicandhistory">{{cite web |url=http://www.musicandhistory.com/music-and-history-by-the-year/196-1935.html |title=1935 |website=MusicAndHistory |access-date=July 24, 2015 |url-status=dead |archive-url=https://web.archive.org/web/20120828144206/http://www.musicandhistory.com/music-and-history-by-the-year/196-1935.html |archive-date=August 28, 2012 }}</ref>
*Ethiopian commander [[Haile Selassie Gugsa]] defected to the Italian side.<ref name="nicolle" />
*The [[Langenberg transmission tower]] was destroyed by a tornado.<ref>{{cite web |url=http://www.chroniknet.de/daly_de.0.html?datum=10.10.1935&year=1935&month=10&day=10 |title=Tageseinträge für 10. Oktober 1935 |website=chroniknet |access-date=July 24, 2015 |archive-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150724115516/http://www.chroniknet.de/daly_de.0.html?datum=10.10.1935&year=1935&month=10&day=10 |url-status=dead }}</ref>
*Six American countries met to define the new border between [[Paraguay]] and [[Bolivia]] after the [[Chaco War]].<ref name="chronology 1935">{{cite web |url=http://www.indiana.edu/~league/1935.htm |title=Chronology 1935 |date=2002 |website=indiana.edu |access-date=July 24, 2015 }}</ref>
==[[October 11]], 1935 (Friday)==
*The League of Nations applied its first round of sanctions against Italy, imposing a general arms embargo against the country. Members were asked to take steps to prevent arms from passing indirectly to Italy through any third party such as Austria.<ref>{{cite news |date=October 11, 1935 |title=First Sanction Allows Nations To Aid Ethiopia | work=[[Brooklyn Daily Eagle]] |pages=1, 3 }}</ref>
*[[Joan Crawford]] and [[Franchot Tone]] were married in [[Englewood Cliffs, New Jersey]].<ref>{{cite news |date=October 14, 1935 |title=Joan Crawford and Franchot Tone Wed Secretly Two Days | work=[[Chicago Daily Tribune]]|page=1 }}</ref>
==[[October 12]], 1935 (Saturday)==
*[[Nazi Germany]] banned [[jazz]] from the radio.<ref name="musicandhistory" />
*An incident took place at the border between [[Siberia]] and [[Manchukuo]] near [[Pogranichny, Primorsky Krai|Grodekovo]]. Events in the clash between a Japanese-Manchukuan detachment and Soviet border guards were disputed, but several were killed on both sides.<ref name="tribune oct 14">{{cite news |date=October 14, 1935 |title=Russia Protests to Tokio Over Guards' Slaying | work=[[Chicago Daily Tribune]]|page=4 }}</ref>
*'''Born:''' [[Tony Kubek]], baseball player, in [[Milwaukee]], [[Wisconsin]]; [[Luciano Pavarotti]], operatic tenor, in [[Modena]], Italy (d. 2007)
==[[October 13]], 1935 (Sunday)==
*The Soviet Union directed a note of protest to Japan over the border incident, accusing the Japanese-Manchukuan detachment of breaching the Siberian border nearly a mile. The Japanese contended that it was the Russians who crossed the border.<ref name="tribune oct 14" />
==[[October 14]], 1935 (Monday)==
*The [[1935 Canadian federal election|Canadian federal election]] was held. The [[Liberal Party of Canada|Liberal Party]] led by [[William Lyon Mackenzie King]] was swept back into power.
*The Turkish government had all Masonic lodges in the country dissolved and their property transferred to the state.<ref>{{cite web |url=http://www.chroniknet.de/daly_de.0.html?datum=14.10.1935&year=1935&month=10&day=14 |title=Tageseinträge für 14. Oktober 1935 |website=chroniknet |access-date=July 24, 2015 |archive-date=September 23, 2015 |archive-url=https://web.archive.org/web/20150923203346/http://www.chroniknet.de/daly_de.0.html?datum=14.10.1935&year=1935&month=10&day=14 }}</ref>
*[[Richard Hauptmann]] was given 30 days' grace to appeal his murder conviction to the [[United States Supreme Court|U.S. Supreme Court]].<ref name="chronicle of the 20th c." />
*The [[Hornibrook Bridge]] opened in [[Bramble Bay]], [[Queensland]], Australia.
*'''Born:''' [[La Monte Young]], minimalist composer, in [[Bern, Idaho]]
==[[October 15]], 1935 (Tuesday)==
*[[Axum]] fell to the Italians.<ref name="nicolle" />
*Germany's ''Naval Gazette'' revealed that 21 [[U-boat]]s had already been launched.<ref>{{cite news |last=Schultz |first=Sigrid |author-link=Sigrid Schultz |date=October 16, 1935 |title=Launching of 21 Submarines by Nazis Revealed | work=[[Chicago Daily Tribune]]|page=22 }}</ref>
*Six one-act plays in the [[Noël Coward]] play cycle ''[[Tonight at 8.30]]'' were performed for the first time in [[Manchester]].
*'''Born:''' [[Barry McGuire]], singer-songwriter, in [[Oklahoma City]], [[Oklahoma]]; [[Bobby Morrow]], sprinter, in [[Harlingen, Texas]] (d. 2020); [[Willie O'Ree]], ice hockey player, in [[Fredericton]], [[New Brunswick]]
==[[October 16]], 1935 (Wednesday)==
*The owners of the [[Nine Mile Point Colliery]] in the [[South Wales Valleys]] announced that the mine was closing indefinitely after a day of fighting between police and striking miners protesting the employment of non-union workers.<ref>{{cite news |date=October 17, 1935 |title=40 Hurt as Riot Rages in Bottom of British Mine | work=[[Chicago Daily Tribune]]|page=10 }}</ref><ref name="chroniknet oct 25">{{cite web |url=http://www.chroniknet.de/daly_de.0.html?datum=25.10.1935&year=1935&month=10&day=25 |title=Tageseinträge für 25. Oktober 1935 |website=chroniknet |access-date=July 24, 2015 |archive-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150724115216/http://www.chroniknet.de/daly_de.0.html?datum=25.10.1935&year=1935&month=10&day=25 |url-status=dead }}</ref>
*The [[Massachusetts Handicap]] horse race was inaugurated.
*'''Born:''' [[Sugar Pie DeSanto]], rhythm and blues singer, in [[Brooklyn]], [[New York (state)|New York]]; [[Sabiha Khanum]], actress, in [[Gujrat City|Gujrat]], [[Punjab Province (British India)|Punjab]], [[British Raj|British India]] (d. 2020)
==[[October 17]], 1935 (Thursday)==
*[[Hans Schweitzer]] was appointed Germany's ''Reichsbeauftragten für künstlerische Formgebung'' (Reich Commissioner for Artistic Design).<ref>{{cite web |url=http://www.chroniknet.de/daly_de.0.html?datum=17.10.1935&year=1935&month=10&day=17 |title=Tageseinträge für 17. Oktober 1935 |website=chroniknet |access-date=July 24, 2015 |archive-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150724114453/http://www.chroniknet.de/daly_de.0.html?datum=17.10.1935&year=1935&month=10&day=17 |url-status=dead }}</ref>
==[[October 18]], 1935 (Friday)==
*The [[1935 Helena earthquake|Helena earthquake]] occurred in [[Montana]].
*The German cabinet convened in Berlin for its first fall session and promulgated a new law forbidding marriage in the case of either party having a hereditary disease. The official publication of the [[Nuremberg Laws]] was postponed due to fears of an international boycott of the [[1936 Summer Olympics]] if they went into effect beforehand.<ref>{{cite news |last=Schultz |first=Sigrid |author-link=Sigrid Schultz |date=October 19, 1935 |title=Hitler Decrees Health Rules for Marriages | work=[[Chicago Daily Tribune]]|page=4 }}</ref>
*In the port of [[Alexandria]], two boiler explosions on the Italian liner ''Ausonia'' killed 6 and injured 20. British vessels in the area including the hospital ship ''[[RFA Maine (1902)|Maine]]'' rendered assistance.<ref>{{cite news |date=October 19, 1935 |title=British Sailors Go to Rescue of Italians as Blasts Kill Six | work=[[Chicago Daily Tribune]]|page=1 }}</ref>
*The historical drama film ''[[The Last Days of Pompeii (1935 film)|The Last Days of Pompeii]]'' was released.
*'''Born:''' [[Peter Boyle]], actor, in [[Norristown, Pennsylvania]] (d. 2006)
*'''Died:''' [[Gaston Lachaise]], 53, French-born American sculptor
==[[October 19]], 1935 (Saturday)==
*The sanctions committee of the League of Nations approved a British proposal for a complete boycott of Italian goods.<ref>{{cite news |date=October 19, 1935 |title=League Votes Boycott on Italy | work=[[Brooklyn Daily Eagle]] |page=1 }}</ref>
==[[October 20]], 1935 (Sunday)==
*The [[Long March]] of the [[People's Liberation Army|Red Army]] of the [[Communist Party of China]] ended. The March lasted 368 days and covered 6,000 miles.<ref>{{cite web |url=http://www.history.com/this-day-in-history/maos-long-march-concludes |title=This Day in History – Mao's Long March Concludes |website=[[History (U.S. TV channel)|History]] |publisher=[[A&E Networks]] |access-date=July 24, 2015 }}</ref>
*'''Born:''' [[Jerry Orbach]], actor and singer, in [[the Bronx]], [[New York (state)|New York]] (d. 2004)
*'''Died:''' [[Adolphus Greely]], 91, American polar explorer and U.S. Army officer; [[Arthur Henderson]], 72, British politician; [[Sidney Smith (cartoonist)|Sidney Smith]], 58, American cartoonist (auto accident)
==[[October 21]], 1935 (Monday)==
*Germany formally ended its membership in the League of Nations. Germany had announced its withdrawal from the League two years earlier, but had to wait until now for all its obligations to expire.<ref>{{cite news|date=October 22, 1935|title=Germany Drops Quietly Out of League Circles|work=[[Chicago Daily Tribune]]|page=2}}</ref>
*[[Argentina]] joined the [[Permanent Court of International Justice|World Court]].<ref>{{cite web|url=http://www.chroniknet.de/daly_de.0.html?datum=21.10.1935&year=1935&month=10&day=21|title=Tageseinträge für 21. Oktober 1935|website=chroniknet|access-date=July 24, 2015|archive-date=July 24, 2015|archive-url=https://web.archive.org/web/20150724113402/http://www.chroniknet.de/daly_de.0.html?datum=21.10.1935&year=1935&month=10&day=21|url-status=dead}}</ref>
*''[[Grant v Australian Knitting Mills]]'', a landmark case in [[consumer law]], was decided on appeal in the [[Judicial Committee of the Privy Council]] in the U.K.<ref>{{cite BAILII|year=1935|court=UKPC|num=62|format=1}}.</ref>
*'''Born:''' [[Derek Bell (musician)|Derek Bell]], musician, in [[Belfast]], [[Northern Ireland]] (d. 2002)
==[[October 22]], 1935 (Tuesday)==
*The [[1935 Jérémie hurricane|Jérémie hurricane]] left three dead and four injured in [[Santiago de Cuba]]. The {{USS|Houston|CA-30|6}}, bearing President Roosevelt home from a fishing trip, avoided the hurricane.<ref>{{cite news |date=October 22, 1935 |title=Cuban Hurricane Kills Three As President Speeds Home | work=[[Brooklyn Daily Eagle]] |page=1 }}</ref>
*'''Born:''' [[Ann Rule]], true crime writer, in [[Lowell, Michigan]] (d. 2015)
*'''Died:''' [[Tommy Tucker (baseball)|Tommy Tucker]], 71, American baseball player
==[[October 23]], 1935 (Wednesday)==
*[[William Lyon Mackenzie King]] became [[Prime Minister of Canada]] for the third time.
*A [[wildfire|brush fire]] in [[Malibu, California]] threatened the homes of several movie stars. Residences belonging to [[Lionel Atwill]] and [[Charles Farrell]] were destroyed, though they were unoccupied at the time.<ref>{{cite news |date=October 24, 1935 |title=Ring of Flame Perils Film Colony | work=[[Chicago Daily Tribune]]|page=1 }}</ref>
*'''Died:''' [[Charles Demuth]], 51, American watercolorist and painter; [[Dutch Schultz]], 34, American mobster (assassinated)
*[[Dutch Schultz]], [[Abe Landau]], [[Otto Berman]], and [[Bernard "Lulu" Rosencrantz]] are fatally shot at a saloon in [[Newark, New Jersey]] in what will become known as [[The Chophouse Massacre]].
==[[October 24]], 1935 (Thursday)==
*The [[Río Piedras massacre]] occurred at the [[University of Puerto Rico]] in [[Río Piedras, Puerto Rico|Río Piedras]].
*[[Winston Churchill]] warned that [[Nazi Germany]] was a greater threat to peace than the war in Abyssinia. "We cannot afford to see Nazidom in its present phase of cruelty and intolerance, with all its hatreds and all its gleaming weapons, paramount in Europe at the present time", he told the House of Commons.<ref>{{cite web |url=http://hansard.millbanksystems.com/commons/1935/oct/24/international-situation#S5CV0305P0_19351024_HOC_318 |title=International Situation |date=October 24, 1935 |website=[[Hansard]] |access-date=July 24, 2015 |archive-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150724125019/http://hansard.millbanksystems.com/commons/1935/oct/24/international-situation#S5CV0305P0_19351024_HOC_318 }}</ref>
==[[October 25]], 1935 (Friday)==
*2,500 died in [[Haiti]] from floods caused by the [[1935 Jérémie hurricane|Jérémie hurricane]].<ref name="musicandhistory" />
*British Parliament was dissolved and a general election called for November 14.<ref>{{cite web |url=http://hansard.millbanksystems.com/commons/1935/oct/23/prorogation-and-dissolution-of-parliament |title=Prorogation and Dissolution of Parliament |date=October 23, 1935 |website=[[Hansard]] |access-date=July 24, 2015 |archive-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150724125601/http://hansard.millbanksystems.com/commons/1935/oct/23/prorogation-and-dissolution-of-parliament }}</ref>
*[[Clement Attlee]] was elected leader of the [[Labour Party (UK)|Labour Party]].<ref name="chronicle of the 20th c." />
*The underground miners' strike ended in [[South Wales]].<ref name="chroniknet oct 25" />
==[[October 26]], 1935 (Saturday)==
*Due to a food shortage in the [[Rhineland-Palatinate]] and [[Saarland]], [[Nazi Germany]] proclaimed meatless and butterless days for those regions on Mondays, Wednesdays and Fridays.<ref>{{cite news |last=Schultz |first=Sigrid |author-link=Sigrid Schultz |date=October 28, 1935 |title=Nazis Ban Use of Meat, Butter by All But Workers | work=[[Chicago Daily Tribune]]|page=6 }}</ref>
*During a speech commemorating the 13th anniversary of the [[March on Rome]], [[Benito Mussolini]] called international sanctions against Italy "the most odious of injustices".<ref>{{cite news |date=October 27, 1935 |title=Stay Away from Boycott, Il Duce Warns Nations | work=[[Chicago Daily Tribune]]|page=2 }}</ref>
*'''Born:''' [[Gloria Conyers Hewitt]], mathematician, in [[Sumter, South Carolina]]
==[[October 27]], 1935 (Sunday)==
*[[Albert Einstein]] and [[Thomas Mann]] endorsed [[Carl von Ossietzky]] for the [[Nobel Peace Prize]]. Ossietzky had been imprisoned in the [[Esterwegen concentration camp]] since February 1934.<ref>{{cite web |url=http://www.chroniknet.de/daly_de.0.html?datum=27.10.1935&year=1935&month=10&day=27 |title=Tageseinträge für 27. Oktober 1935 |website=chroniknet |access-date=July 24, 2015 |archive-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150724114904/http://www.chroniknet.de/daly_de.0.html?datum=27.10.1935&year=1935&month=10&day=27 |url-status=dead }}</ref>
*'''Born:''' [[Frank Adonis]], actor, in [[Brooklyn]], [[New York (state)|New York]] (d. 2018)
==[[October 28]], 1935 (Monday)==
*Civilians in [[Mek'ele]] were ordered to evacuate in anticipation of an Italian offensive.<ref>{{cite news |date=October 28, 1935 |title=Italy Advances As Ethiops Order Makale Vacated | work=[[Brooklyn Daily Eagle]] |page=1 }}</ref>
==[[October 29]], 1935 (Tuesday)==
*Mussolini proclaimed food restrictions, going into effect November 5, in order to fight the effects of boycotting and sanctions. Butcher shops were to close on Tuesdays and were forbidden from selling beef, veal, mutton, lamb or pork on Wednesdays. Since butcher shops already usually closed on Thursdays and most Italians refrained from eating meat on Fridays, the decree amounted to a half-week ban on meat.<ref>{{cite news |date=October 30, 1935 |title=Mussolini Puts Italy on Diet to Fight Boycott | work=[[Chicago Daily Tribune]]|page=1 }}</ref>
*Nazi Germany partially lifted the February 21 gag order on Jewish philosopher [[Martin Buber]]. He was permitted to continue his teaching activities as they promoted the emigration of young Jews to Palestine.<ref>{{cite web |url=http://www.chroniknet.de/daly_de.0.html?datum=29.10.1935&year=1935&month=10&day=29 |title=Tageseinträge für 29. Oktober 1935 |website=chroniknet |access-date=July 24, 2015 |archive-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150724120624/http://www.chroniknet.de/daly_de.0.html?datum=29.10.1935&year=1935&month=10&day=29 |url-status=dead }}</ref>
*'''Born:''' [[Isao Takahata]], filmmaker and co-founder of [[Studio Ghibli]], in [[Ise, Mie]], Japan (d. 2018)
==[[October 30]], 1935 (Wednesday)==
*Nazi Germany announced a general inspection of all vehicles in the country to determine and catalogue their suitability for military use in the event of an emergency.<ref>{{cite news |date=October 30, 1935 |title=Hitler Orders Test of Reich Motorcars For an Emergency | work=[[Brooklyn Daily Eagle]] |page=1 }}</ref>
*Germany instructed its ambassador in Britain to lodge a protest over an article written by [[Winston Churchill]] in ''[[The Strand Magazine]]''. In the article, Churchill wrote that "history is replete with examples of men who have risen to power by employing stern, grim, wicked and even frightful methods, but who, nevertheless, when their life is revealed as a whole, have been regarded as great figures whose lives have enriched the story of mankind. So may it be with Hitler." According to Churchill, time would tell whether Hitler would go down in history as "a monster or a hero."<ref>{{cite news |date=October 31, 1935 |title=Hitler Insulted by Churchill, Berlin Protests | work=[[Chicago Daily Tribune]]|page=5 }}</ref><ref>{{cite web |url=http://www.winstonchurchill.org/publications/finest-hour/finest-hour-156/the-truth-about-hitler-1935-hitler-and-his-choice-1937 |title=Comparative Texts – "The Truth about Hitler," 1935 "Hitler and His Choice," 1937 |date=2012 |website=The Churchill Centre |access-date=July 24, 2015 }}</ref>
*'''Born:''' [[Robert Caro]], journalist and author, in New York City; [[Agota Kristof]], writer, in [[Csikvánd]], Hungary (d. 2011); [[Jim Perry (baseball)|Jim Perry]], baseball player, in [[Williamston, North Carolina]]
==[[October 31]], 1935 (Thursday)==
*Angry Italians staged anti-British demonstrations in Rome. Students shouted anti-British slogans and vandalized shop signs that were written in English.<ref>{{cite news |date=November 1, 1935 |title=Angry Italians Riot Against Britain in Rome | work=[[Chicago Daily Tribune]]|page=1 }}</ref>
*In [[Chicago]], [[Big Joe Williams]] and the Washboard Blues Singers made the first recording of the classic blues song "[[Baby, Please Don't Go]]".
*The British government said it would raise the school-leaving age from 14 to 15.<ref name="chronicle of the 20th c." />
*[[John Henry Lewis]] defeated [[Bob Olin]] in [[St. Louis]] for the World Light Heavyweight Championship of boxing.<ref>{{cite web |url=http://boxrec.com/boxer/17914 |title=John Henry Lewis |website=[[BoxRec]] |access-date=July 24, 2015 |archive-url=https://web.archive.org/web/20150819200547/http://boxrec.com/boxer/17914 |archive-date=August 19, 2015 |url-status=dead }}</ref>
*'''Born:''' [[Ronald Graham]], mathematician, in [[Taft, California]] (d. 2020)
==References==
{{Reflist|30em}}
{{Events by month links}}
[[Category:October|1935]]
[[Category:1935|*1935-10]]
[[Category:Months in the 1930s|*1935-10]]
j4n9z5cb8hueohi07tvgi3qrlzpvlek
Mwbot-rs/Save
0
122864
769885
767809
2026-10-23T23:05:53Z
Mwbot-rs test
52001
Test suite edit
769885
wikitext
text/x-wiki
It has been 1792796753 seconds since the epoch.
os6pvmsfvn9thigacedoxe9qz7av0ar
769887
769885
2026-10-23T23:06:39Z
Mwbot-rs test
52001
Test suite edit
769887
wikitext
text/x-wiki
It has been 1792796798 seconds since the epoch.
4aveocuqoszu4suxel2jfszey65zfjm
769888
769887
2026-10-23T23:06:54Z
Mwbot-rs test
52001
Test suite edit
769888
wikitext
text/x-wiki
It has been 1792796813 seconds since the epoch.
evb3eec2xvimakcpm9j8ea4ptjle18n
769889
769888
2026-10-23T23:06:58Z
Mwbot-rs test
52001
Test suite edit
769889
wikitext
text/x-wiki
It has been 1792796818 seconds since the epoch.
mjyjtgqycyzrrqah5f55enmr03ujn59
User:SongVĩ.Bot II
2
124239
769838
769497
2026-10-23T17:00:32Z
SongVĩ.Bot II
52414
[[User:SongVĩ.Bot II|Task 0]]: Đã 1760 ngày...
769838
wikitext
text/x-wiki
Cập nhật lần cuối: 24-10-2026
Đã 1760 ngày...
f4y3dhfa39knl0s7vu6ume0392j5q68
File:MP sounds-pwb.png
6
152343
769936
769758
2026-10-24T10:28:06Z
Pywikibot-test
23124
Pywikibot-test uploaded a new version of [[File:MP sounds-pwb.png]]
764329
wikitext
text/x-wiki
Flaticon is fine and we are working in der nãhe in
gc94fkpyui3h0farq9cef4ji1hk3ari
769937
769936
2026-10-24T10:28:46Z
Pywikibot-test
23124
Pywikibot-test uploaded a new version of [[File:MP sounds-pwb.png]]
764329
wikitext
text/x-wiki
Flaticon is fine and we are working in der nãhe in
gc94fkpyui3h0farq9cef4ji1hk3ari
File:MP sounds-pwb-chunked.png
6
155656
769938
769759
2026-10-24T10:29:06Z
Pywikibot-test
23124
Pywikibot-test uploaded a new version of [[File:MP sounds-pwb-chunked.png]]
764321
wikitext
text/x-wiki
pywikibot test
[[Category:speaker]]
aozwwno2cjpgcdi00uxzsb2sf3vck96
User:Unicodesnowman/DeleteMark
2
166403
769930
769753
2026-10-24T10:23:20Z
Pywikibot-test
23124
pywikibot unit test. Do NOT actually delete.
769930
wikitext
text/x-wiki
{{delete|1=pywikibot unit test. Do NOT actually delete.}}
Pywikibot deletion test.
dcftqsyyicveaaxsx5yic8hvklv2iue
769931
769930
2026-10-24T10:23:30Z
Pywikibot-test
23124
Pywikibot unit test
769931
wikitext
text/x-wiki
Pywikibot deletion test.
l5a6iw696mbwr7beiudqo7xxpg7k54m
GoldenEye (album)
0
175516
769905
768866
2026-10-24T02:41:06Z
King Hines
76392
/* Charts */
769905
wikitext
text/x-wiki
{{Infobox album
| name = GoldenEye
| type = studio
| artist = [[James Bond (group)|James Bond]]
| cover =
| alt =
| released = 14 November 1995
| recorded = 1994–1995
| venue =
| studio =
| genre = {{hlist|[[Comedy rap]]|[[pop-rap]]}}
| length =
| label = [[EMI Records|EMI]]
| producer =
| prev_title = [[License to Kill (album)|License to Kill]]
| prev_year = 1989
| next_title = [[Tomorrow Never Dies (album)|Tomorrow Never Dies]]
| next_year = 1997
| misc = {{Singles
| name = GoldenEye
| type = studio
| single1 = [[GoldenEye (James Bond song)|GoldenEye]]
| single1date = 31 October 1995
| single2 = [[The Experience of Love (song)|The Experience of Love]]
| single2date = 15 March 1996
| single3 = [[A Pleasant Drive in St. Petersburg]]
| single3date = 24 May 1996
}}
}}
'''''GoldenEye''''' is the seventeenth studio album by British comedy [[pop-rap]] group [[James Bond (group)|James Bond]]. It was released by [[EMI Records]] on 14 November 1995.
== Track listing ==
== Charts and sales ==
== Notes ==
{{notelist}}
== References ==
{{Reflist}}
{{James Bond group}}
{{Authority control}}
[[Category:1995 albums]]
[[Category:James Bond (group) albums]]
[[Category:EMI Records albums]]
dapw76xi8blhnzrnvr50e3or485c674
User:Nardog/sandbox10.js
2
178684
769839
769118
2026-10-23T18:54:27Z
Nardog
40946
769839
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
mw.hook('wikipage.diff').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor($diff) {
if (isOld) {
this.$diff = $diff;
}
this.init();
}
async init() {
this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
dissect() {
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
if (isOld) {
let $lineNums = this.$diff.find('.diff-lineno:last-child');
let $firstLineNum = $lineNums.first();
let firstLineNum = this.getLineNum($firstLineNum);
let $firstContexts = $firstLineNum.closest('tr')
.nextUntil(':not(:has(.diff-context))');
let topLen = firstLineNum + $firstContexts.length - 1;
this.newTop = newLines.slice(0, topLen);
let $lastLineNum, lastLineNum;
if ($lineNums.length > 1) {
$lastLineNum = $lineNums.last();
lastLineNum = this.getLineNum($lastLineNum);
} else {
$lastLineNum = $firstLineNum;
lastLineNum = firstLineNum;
}
let $lastContexts = this.$diff.find('tr:last-child:has(.diff-context)')
.prevUntil(':not(:has(.diff-context))').addBack();
let bottomStart = lastLineNum +
$lastLineNum.closest('tr').nextUntil($lastContexts).filter(
':has(.diff-side-added:is(.diff-context, .diff-addedline))'
).length;
this.newMiddle = newLines.slice(topLen, bottomStart);
this.newBottom = newLines.slice(bottomStart);
this.oldMiddle = $firstContexts.nextUntil($lastContexts)
.find('.diff-side-deleted:not(.diff-empty)')
.get().map(cell => cell.textContent);
} else {
let oldLines = $textarea[0].defaultValue.trimEnd().split('\n');
let oldLen = oldLines.length;
let newLen = newLines.length;
let topLen = 0;
for (let i = 0; i < oldLen && i < newLen; i++) {
if (oldLines[i] !== newLines[i]) break;
topLen++;
}
this.newTop = newLines.slice(0, topLen);
let bottomLen = 0;
if (topLen < oldLen) {
for (let i = 1; i <= oldLen && i <= newLen; i++) {
if (oldLines.at(-i) !== newLines.at(-i)) break;
bottomLen++;
}
}
this.newMiddle = newLines.slice(topLen, -bottomLen || Infinity);
this.newBottom = bottomLen ? newLines.slice(-bottomLen) : [];
this.oldMiddle = oldLines.slice(topLen, -bottomLen || Infinity);
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
outer: for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
continue outer;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
for (let el of new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]')) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === ''
? '/* */ '
: ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce($diff => {
window.asl = new Asl($diff);
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
if (isOld) {
mw.hook('wikipage.diff')[value ? 'add' : 'remove'](Asl.eventHandler);
} else {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
}
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
gdbeav7agwq80f7tovrph1wilpumjf4
769844
769839
2026-10-23T19:06:37Z
Nardog
40946
769844
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
mw.hook('wikipage.diff').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor($diff) {
if (isOld) {
this.$diff = $diff;
}
this.init();
}
async init() {
this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
dissect() {
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
if (isOld) {
let $lineNums = this.$diff.find('.diff-lineno:last-child');
let $firstLineNum = $lineNums.first();
let firstLineNum = this.getLineNum($firstLineNum);
let $firstContexts = $firstLineNum.closest('tr')
.nextUntil(':not(:has(.diff-context))');
let topLen = firstLineNum + $firstContexts.length - 1;
this.newTop = newLines.slice(0, topLen);
let $lastLineNum, lastLineNum;
if ($lineNums.length > 1) {
$lastLineNum = $lineNums.last();
lastLineNum = this.getLineNum($lastLineNum);
} else {
$lastLineNum = $firstLineNum;
lastLineNum = firstLineNum;
}
let $lastContexts = this.$diff.find('tr:last-child:has(.diff-context)')
.prevUntil(':not(:has(.diff-context))').addBack();
let bottomStart = lastLineNum +
$lastLineNum.closest('tr').nextUntil($lastContexts).filter(
':has(.diff-side-added:is(.diff-context, .diff-addedline))'
).length;
this.newMiddle = newLines.slice(topLen, bottomStart);
this.newBottom = newLines.slice(bottomStart);
this.oldMiddle = $firstContexts.nextUntil($lastContexts)
.find('.diff-side-deleted:not(.diff-empty)')
.get().map(cell => cell.textContent);
} else {
let oldLines = $textarea[0].defaultValue.trimEnd().split('\n');
let oldLen = oldLines.length;
let newLen = newLines.length;
let topLen = 0;
for (let i = 0; i < oldLen && i < newLen; i++) {
if (oldLines[i] !== newLines[i]) break;
topLen++;
}
this.newTop = newLines.slice(0, topLen);
let bottomLen = 0;
if (topLen < oldLen) {
for (let i = 1; i <= oldLen && i <= newLen; i++) {
if (oldLines.at(-i) !== newLines.at(-i)) break;
bottomLen++;
}
}
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
outer: for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
continue outer;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
for (let el of new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]')) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === ''
? '/* */ '
: ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce($diff => {
window.asl = new Asl($diff);
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
if (isOld) {
mw.hook('wikipage.diff')[value ? 'add' : 'remove'](Asl.eventHandler);
} else {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
}
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
plnn2l0lklmwc1t56hdgpvz6zay2tob
769845
769844
2026-10-23T19:11:53Z
Nardog
40946
769845
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
mw.hook('wikipage.diff').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor($diff) {
if (isOld) {
this.$diff = $diff;
}
this.init();
}
async init() {
this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
dissect() {
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
if (isOld) {
let $lineNums = this.$diff.find('.diff-lineno:last-child');
let $firstLineNum = $lineNums.first();
let firstLineNum = this.getLineNum($firstLineNum);
let $firstContexts = $firstLineNum.closest('tr')
.nextUntil(':not(:has(.diff-context))');
let topLen = firstLineNum + $firstContexts.length - 1;
this.newTop = newLines.slice(0, topLen);
let $lastLineNum, lastLineNum;
if ($lineNums.length > 1) {
$lastLineNum = $lineNums.last();
lastLineNum = this.getLineNum($lastLineNum);
} else {
$lastLineNum = $firstLineNum;
lastLineNum = firstLineNum;
}
let $lastContexts = this.$diff.find('tr:last-child:has(.diff-context)')
.prevUntil(':not(:has(.diff-context))').addBack();
let bottomStart = lastLineNum +
$lastLineNum.closest('tr').nextUntil($lastContexts).filter(
':has(.diff-side-added:is(.diff-context, .diff-addedline))'
).length;
this.newMiddle = newLines.slice(topLen, bottomStart);
this.newBottom = newLines.slice(bottomStart);
this.oldMiddle = $firstContexts.nextUntil($lastContexts)
.find('.diff-side-deleted:not(.diff-empty)')
.get().map(cell => cell.textContent);
} else {
let oldLines = $textarea[0].defaultValue.trimEnd().split('\n');
let oldLen = oldLines.length;
let newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
this.newTop = newLines.slice(0, topLen);
let bottomLen = 0;
if (topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
outer: for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
continue outer;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
for (let el of new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]')) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === ''
? '/* */ '
: ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce($diff => {
window.asl = new Asl($diff);
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
if (isOld) {
mw.hook('wikipage.diff')[value ? 'add' : 'remove'](Asl.eventHandler);
} else {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
}
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
m0ye3yzqyhhp26dgu500bbkvrqt1shx
769846
769845
2026-10-23T19:19:41Z
Nardog
40946
769846
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
mw.hook('wikipage.diff').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor($diff) {
if (isOld) {
this.$diff = $diff;
}
this.init();
}
async init() {
this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
dissect() {
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
if (isOld) {
let $lineNums = this.$diff.find('.diff-lineno:last-child');
let $firstLineNum = $lineNums.first();
let firstLineNum = this.getLineNum($firstLineNum);
let $firstContexts = $firstLineNum.closest('tr')
.nextUntil(':not(:has(.diff-context))');
let topLen = firstLineNum + $firstContexts.length - 1;
this.newTop = newLines.slice(0, topLen);
let $lastLineNum, lastLineNum;
if ($lineNums.length > 1) {
$lastLineNum = $lineNums.last();
lastLineNum = this.getLineNum($lastLineNum);
} else {
$lastLineNum = $firstLineNum;
lastLineNum = firstLineNum;
}
let $lastContexts = this.$diff.find('tr:last-child:has(.diff-context)')
.prevUntil(':not(:has(.diff-context))').addBack();
let bottomStart = lastLineNum +
$lastLineNum.closest('tr').nextUntil($lastContexts).filter(
':has(.diff-side-added:is(.diff-context, .diff-addedline))'
).length;
this.newMiddle = newLines.slice(topLen, bottomStart);
this.newBottom = newLines.slice(bottomStart);
this.oldMiddle = $firstContexts.nextUntil($lastContexts)
.find('.diff-side-deleted:not(.diff-empty)')
.get().map(cell => cell.textContent);
} else {
let oldLines = $textarea[0].defaultValue.trimEnd().split('\n');
let oldLen = oldLines.length;
let newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
let bottomLen = 0;
if (topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
this.newTop = newLines.slice(0, topLen);
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
outer: for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
continue outer;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
for (let el of new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]')) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === ''
? '/* */ '
: ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce($diff => {
window.asl = new Asl($diff);
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
if (isOld) {
mw.hook('wikipage.diff')[value ? 'add' : 'remove'](Asl.eventHandler);
} else {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
}
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
kbe3u8cqcybk29a3q8k8tjlnrekmap9
769847
769846
2026-10-23T19:20:50Z
Nardog
40946
769847
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
mw.hook('wikipage.diff').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor($diff) {
if (isOld) {
this.$diff = $diff;
}
this.init();
}
async init() {
this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
dissect() {
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
if (isOld) {
let $lineNums = this.$diff.find('.diff-lineno:last-child');
let $firstLineNum = $lineNums.first();
let firstLineNum = this.getLineNum($firstLineNum);
let $firstContexts = $firstLineNum.closest('tr')
.nextUntil(':not(:has(.diff-context))');
let topLen = firstLineNum + $firstContexts.length - 1;
let $lastLineNum, lastLineNum;
if ($lineNums.length > 1) {
$lastLineNum = $lineNums.last();
lastLineNum = this.getLineNum($lastLineNum);
} else {
$lastLineNum = $firstLineNum;
lastLineNum = firstLineNum;
}
let $lastContexts = this.$diff.find('tr:last-child:has(.diff-context)')
.prevUntil(':not(:has(.diff-context))').addBack();
let bottomStart = lastLineNum +
$lastLineNum.closest('tr').nextUntil($lastContexts).filter(
':has(.diff-side-added:is(.diff-context, .diff-addedline))'
).length;
this.newTop = newLines.slice(0, topLen);
this.newMiddle = newLines.slice(topLen, bottomStart);
this.newBottom = newLines.slice(bottomStart);
this.oldMiddle = $firstContexts.nextUntil($lastContexts)
.find('.diff-side-deleted:not(.diff-empty)')
.get().map(cell => cell.textContent);
} else {
let oldLines = $textarea[0].defaultValue.trimEnd().split('\n');
let oldLen = oldLines.length, newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
let bottomLen = 0;
if (topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
this.newTop = newLines.slice(0, topLen);
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
outer: for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
continue outer;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
for (let el of new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]')) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === ''
? '/* */ '
: ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce($diff => {
window.asl = new Asl($diff);
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
if (isOld) {
mw.hook('wikipage.diff')[value ? 'add' : 'remove'](Asl.eventHandler);
} else {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
}
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
3hfcv0cjhz8ujlxp4437bwed0el8lwk
769892
769847
2026-10-24T00:28:06Z
Nardog
40946
769892
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
mw.hook('wikipage.diff').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor($diff) {
if (isOld) {
this.$diff = $diff;
}
this.init();
}
async init() {
await this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
async dissect() {
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
let source = isOld || mw.config.get('wgAction') === 'submit'
? (await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}`
)).source
: $textarea[0].defaultValue;
let oldLines = source.trimEnd().split('\n');
let oldLen = oldLines.length, newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
let bottomLen = 0;
if (topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
this.newTop = newLines.slice(0, topLen);
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
break;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
for (let el of new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]')) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === ''
? '/* */ '
: ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce($diff => {
window.asl = new Asl($diff);
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
if (isOld) {
mw.hook('wikipage.diff')[value ? 'add' : 'remove'](Asl.eventHandler);
} else {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
}
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
cyuhdmxo60m0jlilf3sex87lif8lbq1
769893
769892
2026-10-24T00:35:37Z
Nardog
40946
769893
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
mw.hook('wikipage.diff').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor() {
this.init();
}
async init() {
await this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
async dissect() {
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
let source = isOld || mw.config.get('wgAction') === 'submit'
? (await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}`
)).source
: $textarea[0].defaultValue;
let oldLines = source.trimEnd().split('\n');
let oldLen = oldLines.length, newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
let bottomLen = 0;
if (topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
this.newTop = newLines.slice(0, topLen);
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
break;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
for (let el of new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]')) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === ''
? '/* */ '
: ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce(() => {
window.asl = new Asl();
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
8l97wff4fbq3yy8ta35fg85mcn23ypl
769897
769893
2026-10-24T01:14:45Z
Nardog
40946
769897
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor() {
this.init();
}
async init() {
await this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
async dissect() {
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
let source = isOld || mw.config.get('wgAction') === 'submit'
? (await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}`
)).source
: $textarea[0].defaultValue;
let oldLines = source.trimEnd().split('\n');
let oldLen = oldLines.length, newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
let bottomLen = 0;
if (topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
this.newTop = newLines.slice(0, topLen);
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
break;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
for (let el of new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]')) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === '' ? '/* */ ' : ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce(() => {
window.asl = new Asl();
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
b0bl2i8tsyut6sshfbyqeca7wi9rgv2
769898
769897
2026-10-24T02:11:16Z
Nardog
40946
769898
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
mw.hook('wikipage.diff').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor() {
this.init();
}
async init() {
await this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
async dissect() {
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
let source = isOld || mw.config.get('wgAction') === 'submit'
? (await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}`
)).source
: $textarea[0].defaultValue;
let oldLines = source.trimEnd().split('\n');
let oldLen = oldLines.length, newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
let bottomLen = 0;
if (topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
this.newTop = newLines.slice(0, topLen);
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
break;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
let els = new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]');
for (let el of els) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === ''
? '/* */ '
: ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce(() => {
window.asl = new Asl();
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
aouoo0t7f5iekola3yraflszw32x8ua
769901
769898
2026-10-24T02:31:08Z
Nardog
40946
769901
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
mw.hook('wikipage.diff').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor() {
this.init();
}
async init() {
await this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
async dissect() {
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
let source = isOld || mw.config.get('wgAction') === 'submit'
? (await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}`
)).source
: $textarea[0].defaultValue;
let oldLines = source.trimEnd().split('\n');
let oldLen = oldLines.length, newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
let bottomLen = 0;
if (topLen && topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
this.newTop = newLines.slice(0, topLen);
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
break;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
let els = new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]');
for (let el of els) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === '' ? '/* */ ' : ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce(() => {
window.asl = new Asl();
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
350jsl1rtwz4wv1wjhcfc8zaf1ou242
769902
769901
2026-10-24T02:31:44Z
Nardog
40946
769902
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor() {
this.init();
}
async init() {
await this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
async dissect() {
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
let source = isOld || mw.config.get('wgAction') === 'submit'
? (await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}`
)).source
: $textarea[0].defaultValue;
let oldLines = source.trimEnd().split('\n');
let oldLen = oldLines.length, newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
let bottomLen = 0;
if (topLen && topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
this.newTop = newLines.slice(0, topLen);
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
break;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
let els = new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]');
for (let el of els) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === '' ? '/* */ ' : ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce(() => {
window.asl = new Asl();
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
bxzshh0gz7qo1tryfuy1dc3joitqpqo
769903
769902
2026-10-24T02:38:50Z
Nardog
40946
769903
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor() {
this.init();
}
async init() {
await this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
async dissect() {
let source = isOld || mw.config.get('wgAction') === 'submit'
? (await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}`
)).source
: $textarea[0].defaultValue;
let oldLines = source.trimEnd().split('\n');
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
let oldLen = oldLines.length, newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
let bottomLen = 0;
if (topLen && topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
this.newTop = newLines.slice(0, topLen);
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getLineNum($el) {
let text = $el.text();
let num = Number(text.replace(/\D+/g, ''));
if (!Number.isNaN(num)) {
return num;
}
let match = text.match(/\p{Nd}/gu);
if (!match) {
throw Error(`Cannot extract line number from "${text}"`);
}
return Number(match.map(c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
}).join(''));
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
break;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
let els = new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]');
for (let el of els) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === '' ? '/* */ ' : ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce(() => {
window.asl = new Asl();
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
ajqyf86bgihw6ycmecm2tcihwthnbqs
769904
769903
2026-10-24T02:40:53Z
Nardog
40946
769904
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor() {
this.init();
}
async init() {
await this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
async dissect() {
let source = isOld || mw.config.get('wgAction') === 'submit'
? (await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}`
)).source
: $textarea[0].defaultValue;
let oldLines = source.trimEnd().split('\n');
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
let oldLen = oldLines.length, newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
let bottomLen = 0;
if (topLen && topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
this.newTop = newLines.slice(0, topLen);
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
break;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed
.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
let els = new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]');
for (let el of els) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === '' ? '/* */ ' : ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce(() => {
window.asl = new Asl();
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
ahgoumyrpsr46is2zs0ddat3fjyo550
769906
769904
2026-10-24T02:46:42Z
Nardog
40946
769906
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
['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('editRevId') !== (
formData.get('baseRevId') || formData.get('parentRevId')
);
await mw.loader.using([
'jquery.textSelection', 'mediawiki.util', 'mediawiki.api',
'oojs-ui-widgets', 'oojs-ui.styles.icons-content'
]);
mw.loader.addStyleTag(`.autosectionlink-toggle {
position: absolute;
top: 0;
inset-inline-end: 0;
margin: 0;
}
#wpSummary {
padding-inline-end: 40px;
}
#wpSummary ~ .oo-ui-labelElement-label {
margin-inline-end: 32px;
}`);
let $textarea = $('#wpTextbox1');
let input = OO.ui.infuse(widget);
class Asl {
constructor() {
this.init();
}
async init() {
await this.dissect();
this.getTopmostLevel();
this.getHeadings();
this.setTopSectionLink();
this.getTemplateCodes();
await this.parseTemplates();
await this.expandTemplates();
this.sanitizeHeadings();
await this.resolveCollisions();
this.setSummary();
}
async dissect() {
let source = isOld || mw.config.get('wgAction') === 'submit'
? (await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}`
)).source
: $textarea[0].defaultValue;
let oldLines = source.trimEnd().split('\n');
let newLines = $textarea.textSelection('getContents').trimEnd().split('\n');
let oldLen = oldLines.length, newLen = newLines.length;
let topLen = 0;
while (topLen < oldLen && topLen < newLen &&
oldLines[topLen] === newLines[topLen]
) {
topLen++;
}
let bottomLen = 0;
if (topLen && topLen < oldLen) {
while (bottomLen < oldLen && bottomLen < newLen &&
oldLines.at(-bottomLen - 1) !== newLines.at(-bottomLen - 1)
) {
bottomLen++;
}
}
this.newTop = newLines.slice(0, topLen);
if (bottomLen) {
this.newMiddle = newLines.slice(topLen, -bottomLen);
this.newBottom = newLines.slice(-bottomLen);
this.oldMiddle = oldLines.slice(topLen, -bottomLen);
} else {
this.newMiddle = newLines.slice(topLen);
this.newBottom = [];
this.oldMiddle = oldLines.slice(topLen);
}
while (this.newMiddle[0] === '') {
this.newTop.push(this.newMiddle.shift());
}
}
getTopmostLevel() {
[...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)].forEach(line => {
let match = line.match(Asl.re);
if (!match) return;
if (!this.topmostLevel || match[1].length < this.topmostLevel) {
this.topmostLevel = match[1].length;
}
});
}
getHeadings() {
this.headings = [];
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: section ? [this.newTop[0]] : [];
lines.some(line => {
let match = line.match(Asl.re);
if (!match) return;
if (this.headings.length ||
!this.topmostLevel || match[1].length < this.topmostLevel
) {
this.headings.push({ raw: match[2] });
return section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
section === '0' || !section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
this.templates = new Set();
this.headings.forEach(heading => {
heading.templates = [];
let level = 0, lastIndex;
for (let match of heading.raw.matchAll(/\{\{|\}\}/g)) {
if (match[0] === '{{') {
if (!level) {
lastIndex = match.index;
}
level++;
} else {
if (!level) continue;
level--;
if (!level) {
let code = heading.raw.slice(lastIndex, match.index + 2);
heading.templates.push(code);
this.templates.add(code);
}
}
}
});
}
async parseTemplates() {
let unparsed = [...this.templates]
.filter(code => !Object.hasOwn(Asl.templateCache, code));
if (!unparsed.length) return;
let resolvers = {}, offsets = {}, offset = 0;
unparsed.forEach(code => {
Asl.templateCache[code] = new Promise(r => {
resolvers[code] = r;
});
offsets[offset] = code;
offset += [...code].length + 5;
});
(await new mw.Api().post({
action: 'parse',
title: mw.config.get('wgPageName'),
text: unparsed.map(code => `= ${code} =`).join('\n'),
prop: 'tocdata',
pst: 1,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
})).parse.tocdata.sections.forEach(s => {
if (typeof s.codepointOffset !== 'number' ||
!Object.hasOwn(offsets, s.codepointOffset)
) {
return;
}
let code = offsets[s.codepointOffset];
if (!(Asl.templateCache[code] instanceof Promise)) return;
let result = $($.parseHTML(s.line)).text().replaceAll('&', '&');
Asl.templateCache[code] = result;
resolvers[code](result);
delete resolvers[code];
});
Object.entries(resolvers).forEach(([code, resolver]) => {
Asl.templateCache[code] = null;
resolver(null);
});
}
async expandTemplates() {
for (let [i, heading] of this.headings.entries()) {
// if (/<ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s"<>\[\]]+\]/i.test(heading.raw)) continue;
heading.parsed = heading.raw;
for (let code of heading.templates) {
let result = await Asl.templateCache[code];
if (typeof result === 'string') {
heading.parsed = heading.parsed.replace(code, result);
} else {
heading.parsed = null;
if (!i) return;
break;
}
}
}
}
sanitizeHeadings() {
if (!this.headings.length) return;
this.headings.some((heading, i) => {
if (!heading.parsed) {
return !i;
}
heading.sanitized = heading.parsed.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');
});
this.sectionLink = this.headings[0].sanitized;
}
async resolveCollisions() {
this.ids = [];
if (section > 0) {
let els = new DOMParser().parseFromString(
await new mw.Rest().get(`/v1/page/${
encodeURIComponent(mw.config.get('wgPageName'))
}/html`),
'text/html'
).body.querySelectorAll('[id], [data-mw-section-id]');
for (let el of els) {
if (el.dataset.mwSectionId === section) break;
if (el.id) {
this.ids.push(el.id);
}
}
}
let increment;
this.headings.toReversed().forEach(({ sanitized }) => {
if (!sanitized) return;
let id = sanitized.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
if (this.ids.includes(id)) {
increment = 2;
while (this.ids.includes(`${id}_${increment}`)) {
increment++;
}
this.ids.push(`${id}_${increment}`);
} else {
this.ids.push(id);
increment = null;
}
});
if (increment) {
this.sectionLink += ' ' + increment;
}
}
setSummary() {
let v = input.getValue();
let match = v.match(/^\/\*\s*(.*?)\s*\*\/\s*/);
let prev = match?.[1];
if (prev === this.sectionLink) return;
input.setValue(`${
this.sectionLink
? `/* ${this.sectionLink} */ `
: this.sectionLink === '' ? '/* */ ' : ''
}${match ? v.slice(match[0].length) : v}`);
this.updatePreview();
}
async updatePreview() {
await new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses',
'summary-preview'
]);
let linkText, url = mw.util.getUrl();
if (this.sectionLink) {
linkText = this.sectionLink;
url += '#' + this.sectionLink.replace(/[\s_]+/g, '_').replace(/^_|_$/g, '');
} else if (this.sectionLink === '') {
linkText = mw.msg('autocomment-top');
}
let paren = mw.msg('parentheses').replace(/\$1.+/, '');
let $comment = $('.mw-summary-preview > .comment', form);
let $nodes = $comment.contents();
let $autocomment = $nodes.eq(1).filter('.autocomment:first-child');
if ($autocomment.length && $nodes[0]?.textContent === paren) {
if (linkText) {
$autocomment.children('a').attr('href', url)
.children('bdi').text(linkText);
} else {
$autocomment.remove();
if ($nodes[2]?.nodeType === 3) {
$nodes[2].textContent = $nodes[2].textContent.trimStart();
}
}
} else if (linkText) {
let rtl = document.body.classList.contains('sitedir-rtl');
$autocomment = $('<span>').addClass('autocomment').append(
$('<a>').attr({
href: url,
title: mw.config.get('wgPageName').replaceAll('_', ' ')
}).text(rtl ? '←' : '→').append(
$('<bdi>').attr('dir', rtl ? 'rtl' : 'ltr').text(linkText)
),
mw.message('colon-separator').parseDom()
);
if ($comment.length) {
$comment.prepend(paren, $autocomment);
if ($nodes[0]?.nodeType === 3) {
let text = $nodes[0].textContent;
if (text.startsWith(paren)) {
text = text.slice(paren.length);
}
$nodes[0].textContent = ' ' + text;
}
} else {
$('.mw-summary-preview', form).append(
mw.message('summary-preview').parseDom(),
' ',
$('<span>').addClass('comment').append(
mw.message('parentheses', $autocomment).parseDom()
)
);
}
}
}
static re = /^(={1,6})\s*(.+?)\s*\1\s*(?:<!--.+-->\s*)?$/;
static templateCache = {};
static eventHandler = mw.util.debounce(() => {
window.asl = new Asl();
console.log(asl);
}, 500);
static toggleButton = new OO.ui.ToggleButtonWidget({
classes: ['autosectionlink-toggle'],
framed: false,
icon: 'update',
invisibleLabel: true,
label: 'Auto-update section link'
}).on('change', value => {
$textarea[value ? 'on' : 'off']('input', Asl.eventHandler);
mw.hook('ext.CodeMirror.input')[value ? 'add' : 'remove'](Asl.eventHandler);
Asl.toggleButton.setFlags({ progressive: value });
});
}
window.aslEh = Asl.eventHandler;
input.$input.after(Asl.toggleButton.$element);
Asl.toggleButton.emit('click');
});
p8zr45frej2cdqu97dc5l14rl926j6a
Wikipedia:AI noticeboard/DVRTed
4
178751
769915
769831
2026-10-24T09:44:22Z
DVRTed
55993
Updated row for [[BS Nymph]] (using [[User:DVRTed/AINB-helper|AINB-helper]])
769915
wikitext
text/x-wiki
{{AINB case banner |cleanup=analysis |llmprod= |conduct= |case_type=}}
== Discussion ==
All around me are familiar faces... [[User:DVRTed|DVRTed]] ([[User talk:DVRTed|talk]]) 05:49, 23 October 2026 (UTC)
:test reply breaking the script [[User:DVRTed|DVRTed]] ([[User talk:DVRTed|talk]]) 05:49, 23 October 2026 (UTC)
::test again [[User:DVRTed|DVRTed]] ([[User talk:DVRTed|talk]]) 05:49, 23 October 2026 (UTC)
:::test3 [[User:DVRTed|DVRTed]] ([[User talk:DVRTed|talk]]) 05:50, 23 October 2026 (UTC)
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-10-12}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=BS Nymph|status=unnecessary|notes=2 edits: [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]]}}
{{AIC article row|article=BS Nymph 2|status=completed|notes=2 edits: [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]]}}
{{AIC article row|article=BS Nymph 3|status=ongoing|notes=2 edits: [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]]}}
{{AIC article row|article=BS Nymph 4|status=tagged|notes=2 edits: [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]]}}
{{AIC article row|article=BS Nymph 5|status=completed|notes=2 edits: [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]]}}
{{AIC article row|article=BS Nymph 6|status=completed|notes=2 edits: [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]]}}
{{AIC article row|article=BS Nymph 7|status=completed|notes=2 edits: [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]]}}
{{AIC article row|article=BS Nymph 8|status=completed|notes=2 edits: [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]]}}
}}
kd1joyek53uvje0g34jueeid54yy4t3
User:Nardog/sandbox11.js
2
178772
769840
769769
2026-10-23T18:56:55Z
Nardog
40946
769840
javascript
text/javascript
mw.hook('wikipage.diff').remove(window.sdcb);
mw.hook('wikipage.diff.wikitextBodyUpdate').remove(window.sdcbu);
$('.smartdiff-link').contents().unwrap().end().remove();
$(':is(.diff-addedline, .diff-addedline, .diff-context) > div, .mw-diff-inline-added, .mw-diff-inline-deleted, .mw-diff-inline-changed').contents().each(function () {
if (this.previousSibling && this.previousSibling.tagName === this.tagName) {
this.previousSibling.textContent += this.textContent;
this.remove();
}
});
window.smartdiffTemplates = [
{
names: ['T', 'Tl'],
namespace: 10,
forceNs: true,
end: 1
},
{
names: ['Tlx'],
namespace: 10,
end: 1
},
{
names: ['U'],
prefix: 'Special:Contributions/',
end: 1
},
{
names: ['Re', 'Reply to', 'Ping'],
prefix: 'Special:Contributions/'
},
{
names: ['About'],
start: 3,
skipEven: true
},
{
names: ['For'],
start: 2
},
{
names: ['Other uses', 'Otheruses'],
end: 1
},
{
names: ['Section link', 'Slink'],
end: 1
},
{
names: ['Redirect'],
skipEven: true,
noRedirectEnd: 1
},
{
names: ['Shortcut'],
noRedirectStart: 1
},
{
names: ['Tracked', 'Phab'],
prefix: 'phab:',
end: 1
},
{
names: [
'Distinguish',
'Main', 'Main article',
'Further',
'See also', 'Seealso'
]
},
{
names: ['Ll'],
end: 1
}
];
mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api'
], () => {
mw.loader.addStyleTag(`body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external) {
color: #36b;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):visited {
color: #636;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):active {
color: #b63;
}`);
class SmartDiff {
constructor($diff) {
this.now = performance.now();
this.$diff = $diff;
this.isView = mw.config.get('wgNamespaceNumber') !== -1 &&
mw.config.get('wgAction') === 'view' &&
$diff.nextAll().is('.diff-hr');
[
['old', '#mw-diff-otitle1 a, #differences-prevlink'],
['new', '#mw-diff-ntitle1 a, #differences-nextlink']
].forEach(([side, selector]) => {
let link = this.$diff[0].querySelector(selector);
let pn = link && mw.util.getParamValue('title', link.search);
if (pn) {
this[side] = {
pn,
ns: mw.Title.newFromText(pn).getNamespaceId()
};
}
});
this.isSamePage = this.old?.pn === this.new?.pn;
this.titles = {};
this.noRedirLinks = new WeakSet();
SmartDiff.sds.set($diff[0], this);
this.init();
}
async init($rows = this.$diff) {
this.side = 'old';
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div);
}
this.side = 'new';
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div);
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, true);
}
let $contexts = $rows.find('.diff-context > div'), clones = [];
for (let [i, div] of $contexts.get().entries()) {
if (i % 2) {
this.side = 'new';
if (this.divHasLink) {
if (this.sidePropUsed) {
await this.processLine(div);
} else {
div.replaceChildren(
...$contexts.eq(i - 1).contents().clone()
);
clones.push(div);
}
}
} else {
this.side = 'old';
await this.processLine(div);
}
}
$(clones).find('.smartdiff-link[title]').each((_, link) => {
this.titles[link.title]?.links.push(link);
});
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, isInlineChange) {
this.divHasLink = false;
this.sidePropUsed = false;
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (isInlineChange) {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.divHasLink) return;
try {
let newDiv = this.unescape(tokens);
this.verify(div, newDiv);
div.replaceChildren(...newDiv.childNodes);
} catch (e) {
debugger;
if (typeof e === 'string') {
this.hasError = true;
console.warn(`SmartDiff error: ${e}`);
} else {
throw e;
}
}
}
tokenize(el, escSelector) {
return [...el.childNodes].flatMap(node => {
if (node.nodeType === 3) {
return node.textContent.split('').map(s => ({ s }));
}
if (node.nodeType !== 1) {
return [];
}
if (escSelector && node.matches(escSelector)) {
return [
{ openingTag: node },
{ esc: this.tokenize(node), tag: node },
{ closingTag: node }
];
}
return [
{ openingTag: node },
...this.tokenize(node, escSelector),
{ closingTag: node }
];
});
}
escapeTags(tokens, selector) {
let openTags = [], data = [];
tokens.forEach((token, i) => {
if (token.openingTag?.matches(selector)) {
openTags.push([token.openingTag, i + 1]);
} else if (token.closingTag && openTags.length &&
token.closingTag.tagName === openTags.at(-1)[0].tagName
) {
data.push([...openTags.pop(), i]);
}
});
data.reverse().forEach(([tag, start, end]) => {
let token = { tag };
token.esc = tokens.splice(start, end - start, token);
});
}
unescapeTags(tokens, selector) {
tokens.splice(0, Infinity, ...tokens.flatMap(token => (
token.esc && (!selector || token.tag.matches(selector))
? token.esc
: [token]
)));
}
unescape(tokens) {
let els = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
els.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.link) {
el = el.cloneNode();
}
els.at(-1).append(el);
els.push(el);
} else if (token.closingTag) {
let el = els.pop();
if (!els.length) {
throw `too many closing tags.\n\n${el.innerHTML}`;
}
if (el.tagName !== token.closingTag.tagName) {
throw `expected </${
token.closingTag.tagName.toLowerCase()
}>, got </${
el.tagName.toLowerCase()
}>.\n\n${els[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
els.at(-1).append(lastText);
}
if (els.length !== 1) {
throw `too many opening tags.\n\n${els[0].innerHTML}`;
}
return els[0];
}
verify(div, newDiv) {
let origHtml = div.innerHTML;
let $comp = $(newDiv).clone();
$comp.find('.smartdiff-link').contents().unwrap();
let comp = $comp.html().replace(/<\/(ins|del)><\1(?: [^>]+)?>/g, '');
if (comp !== origHtml) {
throw `changed substance.\n\nOriginal:\n${origHtml}\n\nCleaned:\n${comp}\n\nFull:\n${newDiv.innerHTML}`;
}
let emptyCells = newDiv.querySelectorAll(':is(ins, del):empty');
if (emptyCells.length) {
console.log('Empty cells found:', ...emptyCells);
mw.notify('Empty cells found');
}
}
async exec(tokens, isSecond) {
let s = tokens.map(token => token.s || '').join('');
let modes = isSecond
? ['url', 'link', 'template', 'gallery']
: ['url', 'link', 'template', 'heading', 'gallery'];
for (let mode of modes) {
await this.search(s, tokens, mode);
}
}
async search(s, tokens, mode, data) {
for (let match of s.matchAll(SmartDiff[mode + 'Re'])) {
await this[mode + 'Callback'](
tokens,
...match.indices.slice(1).map((indices, i) => indices && {
s: match[i + 1],
indices,
tokens,
tokenIndices: SmartDiff.tokenIndices
}),
data
);
}
}
static tokenIndices() {
let count = 0, start;
for (let [i, token] of this.tokens.entries()) {
if (token.s) {
if (count === this.indices[0]) {
start = i;
}
if (count === this.indices[1] - 1) {
return [start, i + 1];
}
count++;
}
}
}
static linkRe = /\[\[\s*(?:<tvar(?: [^>]*)?>\s*)?((?:[^<>\[\]{|}])+?)(?:\s*<\/tvar>|\{\{#translation:\s*\}\})?\s*(?:\||\]\])/dg;
async linkCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (s.startsWith('/')) {
t = await this.getSubpage(s);
} else if (s.startsWith('#') && (!this.isView || !this.isSamePage)) {
t = mw.Title.newFromText(this.getProp() + s);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s);
} else {
t = mw.Title.newFromText(s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
static templateRe = /((?<!{){{\s*(?:(?:#invoke|(?:safe)?subst|msg(?:nw)?|raw|int):\s*)?(?:<tvar(?: [^>]*)?>\s*)?)([^<>\[\]{|}]+?)((?:\s*<\/tvar>)?\s*(\|.*)?(?:}}|$))/dg;
async templateCallback(tokens, pre, mid, post, args) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (pre.s.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} else if (s.startsWith('#')) {
return;
} else if (pre.s.includes('int')) {
t = mw.Title.makeTitle(8, s);
} else if (s.startsWith('/')) {
t = await this.getSubpage(s, 10);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s, 10);
} else {
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
if (match && SmartDiff.magicWords.includes(match[0])) return;
}
t = mw.Title.newFromText(s, 10);
}
if (t) {
this.link(tokens, ...mid.tokenIndices(), t);
}
if (!args) return;
let [postStart, postEnd] = post.tokenIndices();
let newPost = tokens.slice(postStart, postEnd);
await this.search(post.s, newPost, 'template');
if (!t || !SmartDiff.templates) return;
let name = t.getMainText();
for (let data of SmartDiff.templates) {
if (Array.isArray(data?.names) &&
Array.prototype.includes.call(data.names, name)
) {
await this.search(
post.s,
newPost,
'templateArgs',
{ ...data, i: 1 }
);
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /\|\s*(?:([^=|]*?)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, name, mid, data) {
let n = name ? Number(name.s) || name.s : data.i++;
if (typeof n !== 'number' || (
n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven)
)) {
return;
}
if (!mid.s) return;
let s = this.decodeWikitext(mid.s);
if (data.prefix) {
s = data.prefix + s;
}
if (data.suffix) {
s += data.suffix;
}
let t = data.forceNs
? mw.Title.makeTitle(data.namespace, s)
: mw.Title.newFromText(s, data.namespace);
if (!t) return;
this.link(
tokens,
...mid.tokenIndices(),
t,
n >= data.noRedirectStart || n <= data.noRedirectEnd
);
}
static urlRe = /(\[(?=.+\]))?((?:\bhttps?:)?\/\/[^\s"<>\[\]{|}]*)([^\s"<>\[\]{|}])/dg;
urlCallback(tokens, pre, mid, last) {
let omitLast;
if (!pre) {
if (mid.s.startsWith('//')) return;
if (last.s === ';') {
let match = mid.s.match(/&#?[\da-z]+$/i);
if (match) {
let source = match[0] + last.s;
omitLast = this.decodeWikitext(source) === source;
} else {
omitLast = true;
}
} else {
omitLast = '!,.:?'.includes(last.s) ||
last.s === ')' && mid.s.includes('(');
}
}
let s = mid.s;
if (!omitLast) {
s += last.s;
}
let [midStart, midEnd] = mid.tokenIndices();
this.link(
tokens,
midStart,
omitLast ? midEnd : last.tokenIndices()[1],
this.decodeWikitext(s)
);
}
static headingRe = /^(={1,6})\s*(.+?)\s*(\1)\s*(?=<!--|$)/dg;
headingCallback(tokens, pre, mid, post) {
if (/{{|<\/?ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s\[\]]+\]/i.test(mid.s)) return;
let s = this.decodeWikitext(mid.s).replace(
/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|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').replace(/^[\s_]+|[\s_]+$/g, '');
let t = mw.Title.newFromText(
`${!this.isView || !this.isSamePage ? this.getProp() : ''}#${s}`
);
if (!t) return;
let midIndices = mid.tokenIndices();
if (tokens.slice(...midIndices).some(({ link }) => link)) {
this.link(tokens, ...post.tokenIndices(), t);
this.link(tokens, ...pre.tokenIndices(), t);
} else {
this.link(tokens, ...midIndices, t);
}
}
// https://commons.wikimedia.org/wiki/Special:MediaStatistics
static galleryRe = /^\s*((?:[^<>\[\]{|}])+\.(?:apng|djv|djvu|flac|gif|jpe|jpeg|jpg|jps|kar|m4a|m4b|m4p|m4r|m4v|mid|midi|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpga|oga|ogg|ogm|ogv|ogx|opus|pdf|png|spx|stl|svg|tif|tiff|wav|webm|webp|xcf))\s*(?:\||$)/dgi;
galleryCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t = mw.Title.newFromText(s, 6);
if (t && t.getNamespaceId() !== 6) {
t = mw.Title.makeTitle(6, s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
link(tokens, start, end, mwtOrUrl, noRedirect) {
let indices = [], last;
tokens.slice(start, end).forEach((token, i) => {
if (!token.s && !token.link && !token.esc) return;
if (token.s && !token.link) {
if (!indices.length || last?.link || last?.esc) {
indices.push([start + i]);
}
indices.at(-1)[1] = start + i + 1;
}
last = token;
});
if (indices.some(chunkIndices => chunkIndices.length !== 2)) {
mw.notify('Corrupt chunks found');
debugger;
}
if (!indices.length) return;
let attribs = { class: 'smartdiff-link' }, obj;
if (typeof mwtOrUrl === 'string') {
attribs.class += ' external';
attribs.href = mwtOrUrl;
attribs.rel = 'nofollow';
} else {
let title = mwtOrUrl.toText();
if (title) {
if (Object.hasOwn(this.titles, title)) {
obj = this.titles[title];
if (obj.classes) {
attribs.class += ' ' + obj.classes.join(' ');
}
} else {
obj = { links: [] };
this.titles[title] = obj;
}
let fragment;
if (obj.url) {
attribs.href = obj.url;
fragment = mwtOrUrl.getFragment();
} else {
let params = obj.red && { action: 'edit', redlink: 1 } ||
noRedirect && obj.redirect && { redirect: 'no' };
if (obj.canonical) {
attribs.href = mw.util.getUrl(obj.canonical, params);
fragment = mwtOrUrl.getFragment();
} else {
attribs.href = mwtOrUrl.getUrl(params);
}
}
if (fragment) {
attribs.href += mw.util.getUrl('#' + fragment);
}
attribs.title = obj.canonical || title;
} else {
attribs.class += ' mw-selflink-fragment';
attribs.href = mwtOrUrl.getUrl();
}
}
let links = indices.reverse().map(chunkIndices => (
this.linkChunk(tokens, ...chunkIndices, attribs)
));
if (obj && !obj.processed) {
obj.links.push(...links);
if (noRedirect) {
links.forEach(link => {
this.noRedirLinks.add(link);
});
}
}
this.divHasLink = true;
}
linkChunk(tokens, start, end, attribs) {
let newStart = start, newEnd = end;
let mid = tokens.slice(start, end);
let midTags = mid.filter(token => token.openingTag || token.closingTag);
for (let i = 0; ; i += 2) {
if (tokens[newStart - 1]?.openingTag && midTags[i]?.closingTag &&
tokens[newStart - 1].openingTag.tagName === midTags[i].closingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
} else {
break;
}
}
for (let i = midTags.length - 1; ; i--) {
if (tokens[newEnd]?.closingTag && midTags[i]?.openingTag &&
tokens[newEnd].closingTag.tagName === midTags[i].openingTag.tagName
) {
midTags.push(tokens[newEnd]);
newEnd++;
} else {
break;
}
}
if (start - newStart) {
console.log(`Moved up ${start - newStart} times`);
}
if (start - newStart > 1) {
mw.notify(`Moved up ${start - newStart} times`);
}
if (newEnd - end) {
console.log(`Moved down ${newEnd - end} times`);
}
if (newEnd - end > 1) {
mw.notify(`Moved down ${newEnd - end} times`);
}
let link = $('<a>').attr(attribs)[0];
let pre = [{ openingTag: link, link }];
let post = [{ closingTag: link, link }];
if (midTags[0]?.closingTag) {
pre.unshift({ ...midTags[0] });
pre.push({ openingTag: midTags[0].closingTag });
}
if (midTags.at(-1)?.openingTag) {
post.unshift({ closingTag: midTags.at(-1).openingTag });
post.push({ ...midTags.at(-1) });
}
tokens.splice(newEnd, 0, ...post);
tokens.splice(newStart, 0, ...pre);
mid.forEach(token => {
if (token.s) {
token.link = link;
}
});
return link;
}
decodeWikitext(text, decodePercents) {
let s = text;
if (decodePercents) {
try {
s = decodeURIComponent(s);
} catch {}
}
return $($.parseHTML(s)).text();
}
async getSubpage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
return mw.Title.newFromText(
this.getProp() + s.replace(/\/+$/, '')
);
}
return mw.Title.newFromText(s, fallbackNs);
}
async getParentPage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
let chunks = s.split('/');
let levelCount = chunks.findIndex(v => v !== '..');
let sup = this.getProp().split('/').slice(0, -levelCount).join('/');
if (sup) {
let sub = chunks.slice(levelCount).join('/').replace(/\/+$/, '');
return mw.Title.newFromText(sub ? `${sup}/${sub}` : sup);
}
}
return mw.Title.newFromText(s, fallbackNs);
}
async canHaveSubpages(ns = this.getProp('ns')) {
SmartDiff.subNs = SmartDiff.subNs ||
mw.config.get('wgVisualEditorConfig')?.namespacesWithSubpages ||
Object.values((await new mw.Api().get({
action: 'query',
meta: 'siteinfo',
siprop: 'namespaces',
formatversion: 2
})).query.namespaces).filter(n => n.subpages).map(n => n.id);
return SmartDiff.subNs.includes(ns);
}
getProp(n = 'pn') {
if (this[this.side]) {
this.sidePropUsed = !this.isSamePage;
return this[this.side][n];
}
if (Object.hasOwn(this, n)) {
return this[n];
}
this.pn = mw.config.get('wgPageName');
this.ns = mw.config.get('wgNamespaceNumber');
return this[n];
}
async queryTitles() {
let unprocessed = Object.entries(this.titles)
.filter(([, v]) => !v.processed).slice(0, 500).map(([k]) => k);
while (unprocessed.length) {
let curTitles = unprocessed.slice(0, 50);
curTitles.forEach(title => {
this.titles[title].processed = true;
});
let { query } = await new mw.Api().post({
action: 'query',
titles: curTitles,
iwurl: 1,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: this.getProp(),
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.pages?.forEach(p => {
let obj = (this.titles[p.title] ??= {});
let classes = p.linkclasses || [];
if (p.missing && !p.known) {
classes.push('new');
obj.red = true;
} else if (p.redirect) {
obj.redirect = true;
}
if (classes.length) {
obj.classes = classes;
}
});
query.interwiki?.forEach(iw => {
this.titles[iw.title] = { classes: ['extiw'], url: iw.url };
});
query.normalized?.forEach(({ from, to }) => {
this.titles[from] = {
...this.titles[from],
...this.titles[to],
canonical: to
};
});
curTitles.forEach(title => {
let obj = this.titles[title];
if (obj.classes) {
$(obj.links).addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$(obj.links).attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
noRedirLinks.has(this) && { redirect: 'no' }
) + this.hash;
});
} else {
let url = obj.url ||
obj.red && mw.util.getUrl(
obj.canonical || title,
{ action: 'edit', redlink: 1 }
) ||
obj.canonical && mw.util.getUrl(obj.canonical);
if (url) {
$(obj.links).attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$(obj.links).attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
static sds = new WeakMap();
static magicWords = [
'!', '=', 'BASEPAGENAME', 'BASEPAGENAME:', 'BASEPAGENAMEE', 'BASEPAGENAMEE:',
'canonicalurl:', 'CURRENTDAY', 'CURRENTDAY2', 'CURRENTDAYNAME', 'CURRENTDOW',
'CURRENTHOUR', 'CURRENTMONTH', 'CURRENTMONTH1', 'CURRENTMONTHABBREV',
'CURRENTMONTHNAME', 'CURRENTMONTHNAMEGEN', 'CURRENTTIME', 'CURRENTTIMESTAMP',
'CURRENTVERSION', 'CURRENTWEEK', 'CURRENTYEAR', 'DEFAULTCATEGORYSORT:',
'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DISPLAYTITLE:', 'filepath:', 'formatnum:',
'FULLPAGENAME', 'FULLPAGENAME:', 'FULLPAGENAMEE', 'FULLPAGENAMEE:', 'fullurl:',
'gender:', 'lc:', 'lcfirst:', 'LOCALDAY', 'LOCALDAY2', 'LOCALDAYNAME',
'LOCALDOW', 'LOCALHOUR', 'LOCALMONTH', 'LOCALMONTH1', 'LOCALMONTHABBREV',
'LOCALMONTHNAME', 'LOCALMONTHNAMEGEN', 'LOCALTIME', 'LOCALTIMESTAMP',
'LOCALWEEK', 'LOCALYEAR','NAMESPACE', 'NAMESPACE:', 'NAMESPACEE', 'NAMESPACEE:',
'NAMESPACENUMBER', 'NAMESPACENUMBER:', 'ns:', 'NUMBEROFACTIVEUSERS',
'NUMBEROFARTICLES', 'NUMBEROFEDITS', 'NUMBEROFFILES', 'NUMBEROFPAGES',
'NUMBEROFUSERS', 'padleft:', 'PAGENAME', 'PAGENAMEE', 'PAGESINCAT:',
'PAGESINCATEGORY:', 'plural:', 'REVISIONDAY', 'REVISIONDAY:', 'REVISIONDAY2',
'REVISIONDAY2:', 'REVISIONID', 'REVISIONID:', 'REVISIONMONTH', 'REVISIONMONTH:',
'REVISIONMONTH1', 'REVISIONMONTH1:', 'REVISIONSIZE', 'REVISIONTIMESTAMP',
'REVISIONTIMESTAMP:', 'REVISIONUSER', 'REVISIONUSER:', 'REVISIONYEAR',
'REVISIONYEAR:', 'ROOTPAGENAME', 'ROOTPAGENAME:', 'ROOTPAGENAMEE',
'ROOTPAGENAMEE:', 'SHORTDESC:', 'SUBJECTPAGENAME', 'SUBJECTPAGENAME:',
'SUBJECTPAGENAMEE', 'SUBJECTPAGENAMEE:', 'SUBJECTSPACE', 'SUBJECTSPACE:',
'SUBJECTSPACEE', 'SUBJECTSPACEE:', 'SUBPAGENAME', 'SUBPAGENAME:',
'SUBPAGENAMEE', 'SUBPAGENAMEE:', 'TALKPAGENAME', 'TALKPAGENAME:',
'TALKPAGENAMEE', 'TALKPAGENAMEE:', 'TALKSPACE', 'TALKSPACE:', 'TALKSPACEE',
'TALKSPACEE:', 'uc:', 'ucfirst:', 'urlencode:'
];
}
if (Array.isArray(window.smartdiffMagicWords)) {
SmartDiff.magicWords.push(...window.smartdiffMagicWords);
}
if (Array.isArray(window.smartdiffTemplates)) {
SmartDiff.templates = window.smartdiffTemplates;
}
mw.hook('wikipage.diff').add((window.sdcb = ($diff => {
window.sd = new SmartDiff($diff);
})));
mw.hook('wikipage.diff.wikitextBodyUpdate').add((window.sdcbu = ($rows => {
let $diff = $rows.closest('.diff');
let sd = SmartDiff.sds.get($diff[0]);
if (sd) {
sd.init($rows.filter(function () {
return this.checkVisibility();
}));
} else {
window.sd = new SmartDiff($diff);
}
})));
});
64mgcdktahye29qxmfbi2aeeb58ied5
769841
769840
2026-10-23T18:57:33Z
Nardog
40946
769841
javascript
text/javascript
mw.hook('wikipage.diff').remove(window.sdcb);
mw.hook('wikipage.diff.wikitextBodyUpdate').remove(window.sdcbu);
$('.smartdiff-link').contents().unwrap().end().remove();
$(':is(.diff-addedline, .diff-addedline, .diff-context) > div, .mw-diff-inline-added, .mw-diff-inline-deleted, .mw-diff-inline-changed').contents().each(function () {
if (this.previousSibling && this.previousSibling.tagName === this.tagName) {
this.previousSibling.textContent += this.textContent;
this.remove();
}
});
window.smartdiffTemplates = [
{
names: ['T', 'Tl'],
namespace: 10,
forceNs: true,
end: 1
},
{
names: ['Tlx'],
namespace: 10,
end: 1
},
{
names: ['U'],
prefix: 'Special:Contributions/',
end: 1
},
{
names: ['Re', 'Reply to', 'Ping'],
prefix: 'Special:Contributions/'
},
{
names: ['About'],
start: 3,
skipEven: true
},
{
names: ['For'],
start: 2
},
{
names: ['Other uses', 'Otheruses'],
end: 1
},
{
names: ['Section link', 'Slink'],
end: 1
},
{
names: ['Redirect'],
skipEven: true,
noRedirectEnd: 1
},
{
names: ['Shortcut'],
noRedirectStart: 1
},
{
names: ['Tracked', 'Phab'],
prefix: 'phab:',
end: 1
},
{
names: [
'Distinguish',
'Main', 'Main article',
'Further',
'See also', 'Seealso'
]
},
{
names: ['Ll'],
end: 1
}
];
mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api'
], () => {
mw.loader.addStyleTag(`body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external) {
color: #36b;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):visited {
color: #636;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):active {
color: #b63;
}`);
class SmartDiff {
constructor($diff) {
this.now = performance.now();
this.$diff = $diff;
this.isView = mw.config.get('wgNamespaceNumber') !== -1 &&
mw.config.get('wgAction') === 'view' &&
$diff.nextAll().is('.diff-hr');
[
['old', '#mw-diff-otitle1 a, #differences-prevlink'],
['new', '#mw-diff-ntitle1 a, #differences-nextlink']
].forEach(([side, selector]) => {
let link = this.$diff[0].querySelector(selector);
let pn = link && mw.util.getParamValue('title', link.search);
if (pn) {
this[side] = {
pn,
ns: mw.Title.newFromText(pn).getNamespaceId()
};
}
});
this.isSamePage = this.old?.pn === this.new?.pn;
this.titles = {};
this.noRedirLinks = new WeakSet();
SmartDiff.sds.set($diff[0], this);
this.init();
}
async init($rows = this.$diff) {
this.side = 'old';
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div);
}
this.side = 'new';
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div);
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, true);
}
let $contexts = $rows.find('.diff-context > div'), clones = [];
for (let [i, div] of $contexts.get().entries()) {
if (i % 2) {
this.side = 'new';
if (this.divHasLink) {
if (this.sidePropUsed) {
await this.processLine(div);
} else {
div.replaceChildren(
...$contexts.eq(i - 1).contents().clone()
);
clones.push(div);
}
}
} else {
this.side = 'old';
await this.processLine(div);
}
}
$(clones).find('.smartdiff-link[title]').each((_, link) => {
this.titles[link.title]?.links.push(link);
});
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, isInlineChange) {
this.divHasLink = false;
this.sidePropUsed = false;
if (div.querySelector('a[href]:not([href^="#"])')) return;
let tokens;
if (isInlineChange) {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.divHasLink) return;
try {
let newDiv = this.unescape(tokens);
this.verify(div, newDiv);
div.replaceChildren(...newDiv.childNodes);
} catch (e) {
debugger;
if (typeof e === 'string') {
this.hasError = true;
console.warn(`SmartDiff error: ${e}`);
} else {
throw e;
}
}
}
tokenize(el, escSelector) {
return [...el.childNodes].flatMap(node => {
if (node.nodeType === 3) {
return node.textContent.split('').map(s => ({ s }));
}
if (node.nodeType !== 1) {
return [];
}
if (escSelector && node.matches(escSelector)) {
return [
{ openingTag: node },
{ esc: this.tokenize(node), tag: node },
{ closingTag: node }
];
}
return [
{ openingTag: node },
...this.tokenize(node, escSelector),
{ closingTag: node }
];
});
}
escapeTags(tokens, selector) {
let openTags = [], data = [];
tokens.forEach((token, i) => {
if (token.openingTag?.matches(selector)) {
openTags.push([token.openingTag, i + 1]);
} else if (token.closingTag && openTags.length &&
token.closingTag.tagName === openTags.at(-1)[0].tagName
) {
data.push([...openTags.pop(), i]);
}
});
data.reverse().forEach(([tag, start, end]) => {
let token = { tag };
token.esc = tokens.splice(start, end - start, token);
});
}
unescapeTags(tokens, selector) {
tokens.splice(0, Infinity, ...tokens.flatMap(token => (
token.esc && (!selector || token.tag.matches(selector))
? token.esc
: [token]
)));
}
unescape(tokens) {
let els = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
els.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.link) {
el = el.cloneNode();
}
els.at(-1).append(el);
els.push(el);
} else if (token.closingTag) {
let el = els.pop();
if (!els.length) {
throw `too many closing tags.\n\n${el.innerHTML}`;
}
if (el.tagName !== token.closingTag.tagName) {
throw `expected </${
token.closingTag.tagName.toLowerCase()
}>, got </${
el.tagName.toLowerCase()
}>.\n\n${els[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
els.at(-1).append(lastText);
}
if (els.length !== 1) {
throw `too many opening tags.\n\n${els[0].innerHTML}`;
}
return els[0];
}
verify(div, newDiv) {
let origHtml = div.innerHTML;
let $comp = $(newDiv).clone();
$comp.find('.smartdiff-link').contents().unwrap();
let comp = $comp.html().replace(/<\/(ins|del)><\1(?: [^>]+)?>/g, '');
if (comp !== origHtml) {
throw `changed substance.\n\nOriginal:\n${origHtml}\n\nCleaned:\n${comp}\n\nFull:\n${newDiv.innerHTML}`;
}
let emptyCells = newDiv.querySelectorAll(':is(ins, del):empty');
if (emptyCells.length) {
console.log('Empty cells found:', ...emptyCells);
mw.notify('Empty cells found');
}
}
async exec(tokens, isSecond) {
let s = tokens.map(token => token.s || '').join('');
let modes = isSecond
? ['url', 'link', 'template', 'gallery']
: ['url', 'link', 'template', 'heading', 'gallery'];
for (let mode of modes) {
await this.search(s, tokens, mode);
}
}
async search(s, tokens, mode, data) {
for (let match of s.matchAll(SmartDiff[mode + 'Re'])) {
await this[mode + 'Callback'](
tokens,
...match.indices.slice(1).map((indices, i) => indices && {
s: match[i + 1],
indices,
tokens,
tokenIndices: SmartDiff.tokenIndices
}),
data
);
}
}
static tokenIndices() {
let count = 0, start;
for (let [i, token] of this.tokens.entries()) {
if (token.s) {
if (count === this.indices[0]) {
start = i;
}
if (count === this.indices[1] - 1) {
return [start, i + 1];
}
count++;
}
}
}
static linkRe = /\[\[\s*(?:<tvar(?: [^>]*)?>\s*)?((?:[^<>\[\]{|}])+?)(?:\s*<\/tvar>|\{\{#translation:\s*\}\})?\s*(?:\||\]\])/dg;
async linkCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (s.startsWith('/')) {
t = await this.getSubpage(s);
} else if (s.startsWith('#') && (!this.isView || !this.isSamePage)) {
t = mw.Title.newFromText(this.getProp() + s);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s);
} else {
t = mw.Title.newFromText(s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
static templateRe = /((?<!{){{\s*(?:(?:#invoke|(?:safe)?subst|msg(?:nw)?|raw|int):\s*)?(?:<tvar(?: [^>]*)?>\s*)?)([^<>\[\]{|}]+?)((?:\s*<\/tvar>)?\s*(\|.*)?(?:}}|$))/dg;
async templateCallback(tokens, pre, mid, post, args) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (pre.s.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} else if (s.startsWith('#')) {
return;
} else if (pre.s.includes('int')) {
t = mw.Title.makeTitle(8, s);
} else if (s.startsWith('/')) {
t = await this.getSubpage(s, 10);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s, 10);
} else {
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
if (match && SmartDiff.magicWords.includes(match[0])) return;
}
t = mw.Title.newFromText(s, 10);
}
if (t) {
this.link(tokens, ...mid.tokenIndices(), t);
}
if (!args) return;
let [postStart, postEnd] = post.tokenIndices();
let newPost = tokens.slice(postStart, postEnd);
await this.search(post.s, newPost, 'template');
if (!t || !SmartDiff.templates) return;
let name = t.getMainText();
for (let data of SmartDiff.templates) {
if (Array.isArray(data?.names) &&
Array.prototype.includes.call(data.names, name)
) {
await this.search(
post.s,
newPost,
'templateArgs',
{ ...data, i: 1 }
);
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /\|\s*(?:([^=|]*?)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, name, mid, data) {
let n = name ? Number(name.s) || name.s : data.i++;
if (typeof n !== 'number' || (
n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven)
)) {
return;
}
if (!mid.s) return;
let s = this.decodeWikitext(mid.s);
if (data.prefix) {
s = data.prefix + s;
}
if (data.suffix) {
s += data.suffix;
}
let t = data.forceNs
? mw.Title.makeTitle(data.namespace, s)
: mw.Title.newFromText(s, data.namespace);
if (!t) return;
this.link(
tokens,
...mid.tokenIndices(),
t,
n >= data.noRedirectStart || n <= data.noRedirectEnd
);
}
static urlRe = /(\[(?=.+\]))?((?:\bhttps?:)?\/\/[^\s"<>\[\]{|}]*)([^\s"<>\[\]{|}])/dg;
urlCallback(tokens, pre, mid, last) {
let omitLast;
if (!pre) {
if (mid.s.startsWith('//')) return;
if (last.s === ';') {
let match = mid.s.match(/&#?[\da-z]+$/i);
if (match) {
let source = match[0] + last.s;
omitLast = this.decodeWikitext(source) === source;
} else {
omitLast = true;
}
} else {
omitLast = '!,.:?'.includes(last.s) ||
last.s === ')' && mid.s.includes('(');
}
}
let s = mid.s;
if (!omitLast) {
s += last.s;
}
let [midStart, midEnd] = mid.tokenIndices();
this.link(
tokens,
midStart,
omitLast ? midEnd : last.tokenIndices()[1],
this.decodeWikitext(s)
);
}
static headingRe = /^(={1,6})\s*(.+?)\s*(\1)\s*(?=<!--|$)/dg;
headingCallback(tokens, pre, mid, post) {
if (/{{|<\/?ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s\[\]]+\]/i.test(mid.s)) return;
let s = this.decodeWikitext(mid.s).replace(
/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|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').replace(/^[\s_]+|[\s_]+$/g, '');
let t = mw.Title.newFromText(
`${!this.isView || !this.isSamePage ? this.getProp() : ''}#${s}`
);
if (!t) return;
let midIndices = mid.tokenIndices();
if (tokens.slice(...midIndices).some(({ link }) => link)) {
this.link(tokens, ...post.tokenIndices(), t);
this.link(tokens, ...pre.tokenIndices(), t);
} else {
this.link(tokens, ...midIndices, t);
}
}
// https://commons.wikimedia.org/wiki/Special:MediaStatistics
static galleryRe = /^\s*((?:[^<>\[\]{|}])+\.(?:apng|djv|djvu|flac|gif|jpe|jpeg|jpg|jps|kar|m4a|m4b|m4p|m4r|m4v|mid|midi|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpga|oga|ogg|ogm|ogv|ogx|opus|pdf|png|spx|stl|svg|tif|tiff|wav|webm|webp|xcf))\s*(?:\||$)/dgi;
galleryCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t = mw.Title.newFromText(s, 6);
if (t && t.getNamespaceId() !== 6) {
t = mw.Title.makeTitle(6, s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
link(tokens, start, end, mwtOrUrl, noRedirect) {
let indices = [], last;
tokens.slice(start, end).forEach((token, i) => {
if (!token.s && !token.link && !token.esc) return;
if (token.s && !token.link) {
if (!indices.length || last?.link || last?.esc) {
indices.push([start + i]);
}
indices.at(-1)[1] = start + i + 1;
}
last = token;
});
if (indices.some(chunkIndices => chunkIndices.length !== 2)) {
mw.notify('Corrupt chunks found');
debugger;
}
if (!indices.length) return;
let attribs = { class: 'smartdiff-link' }, obj;
if (typeof mwtOrUrl === 'string') {
attribs.class += ' external';
attribs.href = mwtOrUrl;
attribs.rel = 'nofollow';
} else {
let title = mwtOrUrl.toText();
if (title) {
if (Object.hasOwn(this.titles, title)) {
obj = this.titles[title];
if (obj.classes) {
attribs.class += ' ' + obj.classes.join(' ');
}
} else {
obj = { links: [] };
this.titles[title] = obj;
}
let fragment;
if (obj.url) {
attribs.href = obj.url;
fragment = mwtOrUrl.getFragment();
} else {
let params = obj.red && { action: 'edit', redlink: 1 } ||
noRedirect && obj.redirect && { redirect: 'no' };
if (obj.canonical) {
attribs.href = mw.util.getUrl(obj.canonical, params);
fragment = mwtOrUrl.getFragment();
} else {
attribs.href = mwtOrUrl.getUrl(params);
}
}
if (fragment) {
attribs.href += mw.util.getUrl('#' + fragment);
}
attribs.title = obj.canonical || title;
} else {
attribs.class += ' mw-selflink-fragment';
attribs.href = mwtOrUrl.getUrl();
}
}
let links = indices.reverse().map(chunkIndices => (
this.linkChunk(tokens, ...chunkIndices, attribs)
));
if (obj && !obj.processed) {
obj.links.push(...links);
if (noRedirect) {
links.forEach(link => {
this.noRedirLinks.add(link);
});
}
}
this.divHasLink = true;
}
linkChunk(tokens, start, end, attribs) {
let newStart = start, newEnd = end;
let mid = tokens.slice(start, end);
let midTags = mid.filter(token => token.openingTag || token.closingTag);
for (let i = 0; ; i += 2) {
if (tokens[newStart - 1]?.openingTag && midTags[i]?.closingTag &&
tokens[newStart - 1].openingTag.tagName === midTags[i].closingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
} else {
break;
}
}
for (let i = midTags.length - 1; ; i--) {
if (tokens[newEnd]?.closingTag && midTags[i]?.openingTag &&
tokens[newEnd].closingTag.tagName === midTags[i].openingTag.tagName
) {
midTags.push(tokens[newEnd]);
newEnd++;
} else {
break;
}
}
if (start - newStart) {
console.log(`Moved up ${start - newStart} times`);
}
if (start - newStart > 1) {
mw.notify(`Moved up ${start - newStart} times`);
}
if (newEnd - end) {
console.log(`Moved down ${newEnd - end} times`);
}
if (newEnd - end > 1) {
mw.notify(`Moved down ${newEnd - end} times`);
}
let link = $('<a>').attr(attribs)[0];
let pre = [{ openingTag: link, link }];
let post = [{ closingTag: link, link }];
if (midTags[0]?.closingTag) {
pre.unshift({ ...midTags[0] });
pre.push({ openingTag: midTags[0].closingTag });
}
if (midTags.at(-1)?.openingTag) {
post.unshift({ closingTag: midTags.at(-1).openingTag });
post.push({ ...midTags.at(-1) });
}
tokens.splice(newEnd, 0, ...post);
tokens.splice(newStart, 0, ...pre);
mid.forEach(token => {
if (token.s) {
token.link = link;
}
});
return link;
}
decodeWikitext(text, decodePercents) {
let s = text;
if (decodePercents) {
try {
s = decodeURIComponent(s);
} catch {}
}
return $($.parseHTML(s)).text();
}
async getSubpage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
return mw.Title.newFromText(
this.getProp() + s.replace(/\/+$/, '')
);
}
return mw.Title.newFromText(s, fallbackNs);
}
async getParentPage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
let chunks = s.split('/');
let levelCount = chunks.findIndex(v => v !== '..');
let sup = this.getProp().split('/').slice(0, -levelCount).join('/');
if (sup) {
let sub = chunks.slice(levelCount).join('/').replace(/\/+$/, '');
return mw.Title.newFromText(sub ? `${sup}/${sub}` : sup);
}
}
return mw.Title.newFromText(s, fallbackNs);
}
async canHaveSubpages(ns = this.getProp('ns')) {
SmartDiff.subNs = SmartDiff.subNs ||
mw.config.get('wgVisualEditorConfig')?.namespacesWithSubpages ||
Object.values((await new mw.Api().get({
action: 'query',
meta: 'siteinfo',
siprop: 'namespaces',
formatversion: 2
})).query.namespaces).filter(n => n.subpages).map(n => n.id);
return SmartDiff.subNs.includes(ns);
}
getProp(n = 'pn') {
if (this[this.side]) {
this.sidePropUsed = !this.isSamePage;
return this[this.side][n];
}
if (Object.hasOwn(this, n)) {
return this[n];
}
this.pn = mw.config.get('wgPageName');
this.ns = mw.config.get('wgNamespaceNumber');
return this[n];
}
async queryTitles() {
let unprocessed = Object.entries(this.titles)
.filter(([, v]) => !v.processed).slice(0, 500).map(([k]) => k);
while (unprocessed.length) {
let curTitles = unprocessed.slice(0, 50);
curTitles.forEach(title => {
this.titles[title].processed = true;
});
let { query } = await new mw.Api().post({
action: 'query',
titles: curTitles,
iwurl: 1,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: this.getProp(),
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.pages?.forEach(p => {
let obj = (this.titles[p.title] ??= {});
let classes = p.linkclasses || [];
if (p.missing && !p.known) {
classes.push('new');
obj.red = true;
} else if (p.redirect) {
obj.redirect = true;
}
if (classes.length) {
obj.classes = classes;
}
});
query.interwiki?.forEach(iw => {
this.titles[iw.title] = { classes: ['extiw'], url: iw.url };
});
query.normalized?.forEach(({ from, to }) => {
this.titles[from] = {
...this.titles[from],
...this.titles[to],
canonical: to
};
});
curTitles.forEach(title => {
let obj = this.titles[title];
if (obj.classes) {
$(obj.links).addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$(obj.links).attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
noRedirLinks.has(this) && { redirect: 'no' }
) + this.hash;
});
} else {
let url = obj.url ||
obj.red && mw.util.getUrl(
obj.canonical || title,
{ action: 'edit', redlink: 1 }
) ||
obj.canonical && mw.util.getUrl(obj.canonical);
if (url) {
$(obj.links).attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$(obj.links).attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
static sds = new WeakMap();
static magicWords = [
'!', '=', 'BASEPAGENAME', 'BASEPAGENAME:', 'BASEPAGENAMEE', 'BASEPAGENAMEE:',
'canonicalurl:', 'CURRENTDAY', 'CURRENTDAY2', 'CURRENTDAYNAME', 'CURRENTDOW',
'CURRENTHOUR', 'CURRENTMONTH', 'CURRENTMONTH1', 'CURRENTMONTHABBREV',
'CURRENTMONTHNAME', 'CURRENTMONTHNAMEGEN', 'CURRENTTIME', 'CURRENTTIMESTAMP',
'CURRENTVERSION', 'CURRENTWEEK', 'CURRENTYEAR', 'DEFAULTCATEGORYSORT:',
'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DISPLAYTITLE:', 'filepath:', 'formatnum:',
'FULLPAGENAME', 'FULLPAGENAME:', 'FULLPAGENAMEE', 'FULLPAGENAMEE:', 'fullurl:',
'gender:', 'lc:', 'lcfirst:', 'LOCALDAY', 'LOCALDAY2', 'LOCALDAYNAME',
'LOCALDOW', 'LOCALHOUR', 'LOCALMONTH', 'LOCALMONTH1', 'LOCALMONTHABBREV',
'LOCALMONTHNAME', 'LOCALMONTHNAMEGEN', 'LOCALTIME', 'LOCALTIMESTAMP',
'LOCALWEEK', 'LOCALYEAR','NAMESPACE', 'NAMESPACE:', 'NAMESPACEE', 'NAMESPACEE:',
'NAMESPACENUMBER', 'NAMESPACENUMBER:', 'ns:', 'NUMBEROFACTIVEUSERS',
'NUMBEROFARTICLES', 'NUMBEROFEDITS', 'NUMBEROFFILES', 'NUMBEROFPAGES',
'NUMBEROFUSERS', 'padleft:', 'PAGENAME', 'PAGENAMEE', 'PAGESINCAT:',
'PAGESINCATEGORY:', 'plural:', 'REVISIONDAY', 'REVISIONDAY:', 'REVISIONDAY2',
'REVISIONDAY2:', 'REVISIONID', 'REVISIONID:', 'REVISIONMONTH', 'REVISIONMONTH:',
'REVISIONMONTH1', 'REVISIONMONTH1:', 'REVISIONSIZE', 'REVISIONTIMESTAMP',
'REVISIONTIMESTAMP:', 'REVISIONUSER', 'REVISIONUSER:', 'REVISIONYEAR',
'REVISIONYEAR:', 'ROOTPAGENAME', 'ROOTPAGENAME:', 'ROOTPAGENAMEE',
'ROOTPAGENAMEE:', 'SHORTDESC:', 'SUBJECTPAGENAME', 'SUBJECTPAGENAME:',
'SUBJECTPAGENAMEE', 'SUBJECTPAGENAMEE:', 'SUBJECTSPACE', 'SUBJECTSPACE:',
'SUBJECTSPACEE', 'SUBJECTSPACEE:', 'SUBPAGENAME', 'SUBPAGENAME:',
'SUBPAGENAMEE', 'SUBPAGENAMEE:', 'TALKPAGENAME', 'TALKPAGENAME:',
'TALKPAGENAMEE', 'TALKPAGENAMEE:', 'TALKSPACE', 'TALKSPACE:', 'TALKSPACEE',
'TALKSPACEE:', 'uc:', 'ucfirst:', 'urlencode:'
];
}
if (Array.isArray(window.smartdiffMagicWords)) {
SmartDiff.magicWords.push(...window.smartdiffMagicWords);
}
if (Array.isArray(window.smartdiffTemplates)) {
SmartDiff.templates = window.smartdiffTemplates;
}
mw.hook('wikipage.diff').add((window.sdcb = ($diff => {
window.sd = new SmartDiff($diff);
})));
mw.hook('wikipage.diff.wikitextBodyUpdate').add((window.sdcbu = ($rows => {
let $diff = $rows.closest('.diff');
let sd = SmartDiff.sds.get($diff[0]);
if (sd) {
sd.init($rows.filter(function () {
return this.checkVisibility();
}));
} else {
window.sd = new SmartDiff($diff);
}
})));
});
63anbumxywfqc1hm5s5ipmclq0j38gk
769843
769841
2026-10-23T18:58:45Z
Nardog
40946
769843
javascript
text/javascript
mw.hook('wikipage.diff').remove(window.sdcb);
mw.hook('wikipage.diff.wikitextBodyUpdate').remove(window.sdcbu);
$('.smartdiff-link').contents().unwrap().end().remove();
$(':is(.diff-addedline, .diff-addedline, .diff-context) > div, .mw-diff-inline-added, .mw-diff-inline-deleted, .mw-diff-inline-changed').contents().each(function () {
if (this.previousSibling && this.previousSibling.tagName === this.tagName) {
this.previousSibling.textContent += this.textContent;
this.remove();
}
});
window.smartdiffTemplates = [
{
names: ['T', 'Tl'],
namespace: 10,
forceNs: true,
end: 1
},
{
names: ['Tlx'],
namespace: 10,
end: 1
},
{
names: ['U'],
prefix: 'Special:Contributions/',
end: 1
},
{
names: ['Re', 'Reply to', 'Ping'],
prefix: 'Special:Contributions/'
},
{
names: ['About'],
start: 3,
skipEven: true
},
{
names: ['For'],
start: 2
},
{
names: ['Other uses', 'Otheruses'],
end: 1
},
{
names: ['Section link', 'Slink'],
end: 1
},
{
names: ['Redirect'],
skipEven: true,
noRedirectEnd: 1
},
{
names: ['Shortcut'],
noRedirectStart: 1
},
{
names: ['Tracked', 'Phab'],
prefix: 'phab:',
end: 1
},
{
names: [
'Distinguish',
'Main', 'Main article',
'Further',
'See also', 'Seealso'
]
},
{
names: ['Ll'],
end: 1
}
];
mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api'
], () => {
mw.loader.addStyleTag(`body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external) {
color: #36b;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):visited {
color: #636;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):active {
color: #b63;
}`);
class SmartDiff {
constructor($diff) {
this.now = performance.now();
this.$diff = $diff;
this.isView = mw.config.get('wgNamespaceNumber') !== -1 &&
mw.config.get('wgAction') === 'view' &&
$diff.nextAll().is('.diff-hr');
[
['old', '#mw-diff-otitle1 a, #differences-prevlink'],
['new', '#mw-diff-ntitle1 a, #differences-nextlink']
].forEach(([side, selector]) => {
let link = this.$diff[0].querySelector(selector);
let pn = link && mw.util.getParamValue('title', link.search);
if (pn) {
this[side] = {
pn,
ns: mw.Title.newFromText(pn).getNamespaceId()
};
}
});
this.isSamePage = this.old?.pn === this.new?.pn;
this.titles = {};
this.noRedirLinks = new WeakSet();
SmartDiff.sds.set($diff[0], this);
this.init();
}
async init($rows = this.$diff) {
this.side = 'old';
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div);
}
this.side = 'new';
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div);
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, true);
}
let $contexts = $rows.find('.diff-context > div'), clones = [];
for (let [i, div] of $contexts.get().entries()) {
if (i % 2) {
this.side = 'new';
if (this.divHasLink) {
if (this.sidePropUsed) {
await this.processLine(div);
} else {
div.replaceChildren(
...$contexts.eq(i - 1).contents().clone()
);
clones.push(div);
}
}
} else {
this.side = 'old';
await this.processLine(div);
}
}
$(clones).find('.smartdiff-link[title]').each((_, link) => {
this.titles[link.title]?.links.push(link);
});
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, isInlineChange) {
this.divHasLink = false;
this.sidePropUsed = false;
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (isInlineChange) {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.divHasLink) return;
try {
let newDiv = this.unescape(tokens);
this.verify(div, newDiv);
div.replaceChildren(...newDiv.childNodes);
} catch (e) {
debugger;
if (typeof e === 'string') {
this.hasError = true;
console.warn(`SmartDiff error: ${e}`);
} else {
throw e;
}
}
}
tokenize(el, escSelector) {
return [...el.childNodes].flatMap(node => {
if (node.nodeType === 3) {
return node.textContent.split('').map(s => ({ s }));
}
if (node.nodeType !== 1) {
return [];
}
if (escSelector && node.matches(escSelector)) {
return [
{ openingTag: node },
{ esc: this.tokenize(node), tag: node },
{ closingTag: node }
];
}
return [
{ openingTag: node },
...this.tokenize(node, escSelector),
{ closingTag: node }
];
});
}
escapeTags(tokens, selector) {
let openTags = [], data = [];
tokens.forEach((token, i) => {
if (token.openingTag?.matches(selector)) {
openTags.push([token.openingTag, i + 1]);
} else if (token.closingTag && openTags.length &&
token.closingTag.tagName === openTags.at(-1)[0].tagName
) {
data.push([...openTags.pop(), i]);
}
});
data.reverse().forEach(([tag, start, end]) => {
let token = { tag };
token.esc = tokens.splice(start, end - start, token);
});
}
unescapeTags(tokens, selector) {
tokens.splice(0, Infinity, ...tokens.flatMap(token => (
token.esc && (!selector || token.tag.matches(selector))
? token.esc
: [token]
)));
}
unescape(tokens) {
let els = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
els.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.link) {
el = el.cloneNode();
}
els.at(-1).append(el);
els.push(el);
} else if (token.closingTag) {
let el = els.pop();
if (!els.length) {
throw `too many closing tags.\n\n${el.innerHTML}`;
}
if (el.tagName !== token.closingTag.tagName) {
throw `expected </${
token.closingTag.tagName.toLowerCase()
}>, got </${
el.tagName.toLowerCase()
}>.\n\n${els[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
els.at(-1).append(lastText);
}
if (els.length !== 1) {
throw `too many opening tags.\n\n${els[0].innerHTML}`;
}
return els[0];
}
verify(div, newDiv) {
let origHtml = div.innerHTML;
let $comp = $(newDiv).clone();
$comp.find('.smartdiff-link').contents().unwrap();
let comp = $comp.html().replace(/<\/(ins|del)><\1(?: [^>]+)?>/g, '');
if (comp !== origHtml) {
throw `changed substance.\n\nOriginal:\n${origHtml}\n\nCleaned:\n${comp}\n\nFull:\n${newDiv.innerHTML}`;
}
let emptyCells = newDiv.querySelectorAll(':is(ins, del):empty');
if (emptyCells.length) {
console.log('Empty cells found:', ...emptyCells);
mw.notify('Empty cells found');
}
}
async exec(tokens, isSecond) {
let s = tokens.map(token => token.s || '').join('');
let modes = isSecond
? ['url', 'link', 'template', 'gallery']
: ['url', 'link', 'template', 'heading', 'gallery'];
for (let mode of modes) {
await this.search(s, tokens, mode);
}
}
async search(s, tokens, mode, data) {
for (let match of s.matchAll(SmartDiff[mode + 'Re'])) {
await this[mode + 'Callback'](
tokens,
...match.indices.slice(1).map((indices, i) => indices && {
s: match[i + 1],
indices,
tokens,
tokenIndices: SmartDiff.tokenIndices
}),
data
);
}
}
static tokenIndices() {
let count = 0, start;
for (let [i, token] of this.tokens.entries()) {
if (token.s) {
if (count === this.indices[0]) {
start = i;
}
if (count === this.indices[1] - 1) {
return [start, i + 1];
}
count++;
}
}
}
static linkRe = /\[\[\s*(?:<tvar(?: [^>]*)?>\s*)?((?:[^<>\[\]{|}])+?)(?:\s*<\/tvar>|\{\{#translation:\s*\}\})?\s*(?:\||\]\])/dg;
async linkCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (s.startsWith('/')) {
t = await this.getSubpage(s);
} else if (s.startsWith('#') && (!this.isView || !this.isSamePage)) {
t = mw.Title.newFromText(this.getProp() + s);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s);
} else {
t = mw.Title.newFromText(s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
static templateRe = /((?<!{){{\s*(?:(?:#invoke|(?:safe)?subst|msg(?:nw)?|raw|int):\s*)?(?:<tvar(?: [^>]*)?>\s*)?)([^<>\[\]{|}]+?)((?:\s*<\/tvar>)?\s*(\|.*)?(?:}}|$))/dg;
async templateCallback(tokens, pre, mid, post, args) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (pre.s.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} else if (s.startsWith('#')) {
return;
} else if (pre.s.includes('int')) {
t = mw.Title.makeTitle(8, s);
} else if (s.startsWith('/')) {
t = await this.getSubpage(s, 10);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s, 10);
} else {
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
if (match && SmartDiff.magicWords.includes(match[0])) return;
}
t = mw.Title.newFromText(s, 10);
}
if (t) {
this.link(tokens, ...mid.tokenIndices(), t);
}
if (!args) return;
let [postStart, postEnd] = post.tokenIndices();
let newPost = tokens.slice(postStart, postEnd);
await this.search(post.s, newPost, 'template');
if (!t || !SmartDiff.templates) return;
let name = t.getMainText();
for (let data of SmartDiff.templates) {
if (Array.isArray(data?.names) &&
Array.prototype.includes.call(data.names, name)
) {
await this.search(
post.s,
newPost,
'templateArgs',
{ ...data, i: 1 }
);
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /\|\s*(?:([^=|]*?)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, name, mid, data) {
let n = name ? Number(name.s) || name.s : data.i++;
if (typeof n !== 'number' || (
n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven)
)) {
return;
}
if (!mid.s) return;
let s = this.decodeWikitext(mid.s);
if (data.prefix) {
s = data.prefix + s;
}
if (data.suffix) {
s += data.suffix;
}
let t = data.forceNs
? mw.Title.makeTitle(data.namespace, s)
: mw.Title.newFromText(s, data.namespace);
if (!t) return;
this.link(
tokens,
...mid.tokenIndices(),
t,
n >= data.noRedirectStart || n <= data.noRedirectEnd
);
}
static urlRe = /(\[(?=.+\]))?((?:\bhttps?:)?\/\/[^\s"<>\[\]{|}]*)([^\s"<>\[\]{|}])/dg;
urlCallback(tokens, pre, mid, last) {
let omitLast;
if (!pre) {
if (mid.s.startsWith('//')) return;
if (last.s === ';') {
let match = mid.s.match(/&#?[\da-z]+$/i);
if (match) {
let source = match[0] + last.s;
omitLast = this.decodeWikitext(source) === source;
} else {
omitLast = true;
}
} else {
omitLast = '!,.:?'.includes(last.s) ||
last.s === ')' && mid.s.includes('(');
}
}
let s = mid.s;
if (!omitLast) {
s += last.s;
}
let [midStart, midEnd] = mid.tokenIndices();
this.link(
tokens,
midStart,
omitLast ? midEnd : last.tokenIndices()[1],
this.decodeWikitext(s)
);
}
static headingRe = /^(={1,6})\s*(.+?)\s*(\1)\s*(?=<!--|$)/dg;
headingCallback(tokens, pre, mid, post) {
if (/{{|<\/?ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s\[\]]+\]/i.test(mid.s)) return;
let s = this.decodeWikitext(mid.s).replace(
/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|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').replace(/^[\s_]+|[\s_]+$/g, '');
let t = mw.Title.newFromText(
`${!this.isView || !this.isSamePage ? this.getProp() : ''}#${s}`
);
if (!t) return;
let midIndices = mid.tokenIndices();
if (tokens.slice(...midIndices).some(({ link }) => link)) {
this.link(tokens, ...post.tokenIndices(), t);
this.link(tokens, ...pre.tokenIndices(), t);
} else {
this.link(tokens, ...midIndices, t);
}
}
// https://commons.wikimedia.org/wiki/Special:MediaStatistics
static galleryRe = /^\s*((?:[^<>\[\]{|}])+\.(?:apng|djv|djvu|flac|gif|jpe|jpeg|jpg|jps|kar|m4a|m4b|m4p|m4r|m4v|mid|midi|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpga|oga|ogg|ogm|ogv|ogx|opus|pdf|png|spx|stl|svg|tif|tiff|wav|webm|webp|xcf))\s*(?:\||$)/dgi;
galleryCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t = mw.Title.newFromText(s, 6);
if (t && t.getNamespaceId() !== 6) {
t = mw.Title.makeTitle(6, s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
link(tokens, start, end, mwtOrUrl, noRedirect) {
let indices = [], last;
tokens.slice(start, end).forEach((token, i) => {
if (!token.s && !token.link && !token.esc) return;
if (token.s && !token.link) {
if (!indices.length || last?.link || last?.esc) {
indices.push([start + i]);
}
indices.at(-1)[1] = start + i + 1;
}
last = token;
});
if (indices.some(chunkIndices => chunkIndices.length !== 2)) {
mw.notify('Corrupt chunks found');
debugger;
}
if (!indices.length) return;
let attribs = { class: 'smartdiff-link' }, obj;
if (typeof mwtOrUrl === 'string') {
attribs.class += ' external';
attribs.href = mwtOrUrl;
attribs.rel = 'nofollow';
} else {
let title = mwtOrUrl.toText();
if (title) {
if (Object.hasOwn(this.titles, title)) {
obj = this.titles[title];
if (obj.classes) {
attribs.class += ' ' + obj.classes.join(' ');
}
} else {
obj = { links: [] };
this.titles[title] = obj;
}
let fragment;
if (obj.url) {
attribs.href = obj.url;
fragment = mwtOrUrl.getFragment();
} else {
let params = obj.red && { action: 'edit', redlink: 1 } ||
noRedirect && obj.redirect && { redirect: 'no' };
if (obj.canonical) {
attribs.href = mw.util.getUrl(obj.canonical, params);
fragment = mwtOrUrl.getFragment();
} else {
attribs.href = mwtOrUrl.getUrl(params);
}
}
if (fragment) {
attribs.href += mw.util.getUrl('#' + fragment);
}
attribs.title = obj.canonical || title;
} else {
attribs.class += ' mw-selflink-fragment';
attribs.href = mwtOrUrl.getUrl();
}
}
let links = indices.reverse().map(chunkIndices => (
this.linkChunk(tokens, ...chunkIndices, attribs)
));
if (obj && !obj.processed) {
obj.links.push(...links);
if (noRedirect) {
links.forEach(link => {
this.noRedirLinks.add(link);
});
}
}
this.divHasLink = true;
}
linkChunk(tokens, start, end, attribs) {
let newStart = start, newEnd = end;
let mid = tokens.slice(start, end);
let midTags = mid.filter(token => token.openingTag || token.closingTag);
for (let i = 0; ; i += 2) {
if (tokens[newStart - 1]?.openingTag && midTags[i]?.closingTag &&
tokens[newStart - 1].openingTag.tagName === midTags[i].closingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
} else {
break;
}
}
for (let i = midTags.length - 1; ; i--) {
if (tokens[newEnd]?.closingTag && midTags[i]?.openingTag &&
tokens[newEnd].closingTag.tagName === midTags[i].openingTag.tagName
) {
midTags.push(tokens[newEnd]);
newEnd++;
} else {
break;
}
}
if (start - newStart) {
console.log(`Moved up ${start - newStart} times`);
}
if (start - newStart > 1) {
mw.notify(`Moved up ${start - newStart} times`);
}
if (newEnd - end) {
console.log(`Moved down ${newEnd - end} times`);
}
if (newEnd - end > 1) {
mw.notify(`Moved down ${newEnd - end} times`);
}
let link = $('<a>').attr(attribs)[0];
let pre = [{ openingTag: link, link }];
let post = [{ closingTag: link, link }];
if (midTags[0]?.closingTag) {
pre.unshift({ ...midTags[0] });
pre.push({ openingTag: midTags[0].closingTag });
}
if (midTags.at(-1)?.openingTag) {
post.unshift({ closingTag: midTags.at(-1).openingTag });
post.push({ ...midTags.at(-1) });
}
tokens.splice(newEnd, 0, ...post);
tokens.splice(newStart, 0, ...pre);
mid.forEach(token => {
if (token.s) {
token.link = link;
}
});
return link;
}
decodeWikitext(text, decodePercents) {
let s = text;
if (decodePercents) {
try {
s = decodeURIComponent(s);
} catch {}
}
return $($.parseHTML(s)).text();
}
async getSubpage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
return mw.Title.newFromText(
this.getProp() + s.replace(/\/+$/, '')
);
}
return mw.Title.newFromText(s, fallbackNs);
}
async getParentPage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
let chunks = s.split('/');
let levelCount = chunks.findIndex(v => v !== '..');
let sup = this.getProp().split('/').slice(0, -levelCount).join('/');
if (sup) {
let sub = chunks.slice(levelCount).join('/').replace(/\/+$/, '');
return mw.Title.newFromText(sub ? `${sup}/${sub}` : sup);
}
}
return mw.Title.newFromText(s, fallbackNs);
}
async canHaveSubpages(ns = this.getProp('ns')) {
SmartDiff.subNs = SmartDiff.subNs ||
mw.config.get('wgVisualEditorConfig')?.namespacesWithSubpages ||
Object.values((await new mw.Api().get({
action: 'query',
meta: 'siteinfo',
siprop: 'namespaces',
formatversion: 2
})).query.namespaces).filter(n => n.subpages).map(n => n.id);
return SmartDiff.subNs.includes(ns);
}
getProp(n = 'pn') {
if (this[this.side]) {
this.sidePropUsed = !this.isSamePage;
return this[this.side][n];
}
if (Object.hasOwn(this, n)) {
return this[n];
}
this.pn = mw.config.get('wgPageName');
this.ns = mw.config.get('wgNamespaceNumber');
return this[n];
}
async queryTitles() {
let unprocessed = Object.entries(this.titles)
.filter(([, v]) => !v.processed).slice(0, 500).map(([k]) => k);
while (unprocessed.length) {
let curTitles = unprocessed.slice(0, 50);
curTitles.forEach(title => {
this.titles[title].processed = true;
});
let { query } = await new mw.Api().post({
action: 'query',
titles: curTitles,
iwurl: 1,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: this.getProp(),
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.pages?.forEach(p => {
let obj = (this.titles[p.title] ??= {});
let classes = p.linkclasses || [];
if (p.missing && !p.known) {
classes.push('new');
obj.red = true;
} else if (p.redirect) {
obj.redirect = true;
}
if (classes.length) {
obj.classes = classes;
}
});
query.interwiki?.forEach(iw => {
this.titles[iw.title] = { classes: ['extiw'], url: iw.url };
});
query.normalized?.forEach(({ from, to }) => {
this.titles[from] = {
...this.titles[from],
...this.titles[to],
canonical: to
};
});
curTitles.forEach(title => {
let obj = this.titles[title];
if (obj.classes) {
$(obj.links).addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$(obj.links).attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
noRedirLinks.has(this) && { redirect: 'no' }
) + this.hash;
});
} else {
let url = obj.url ||
obj.red && mw.util.getUrl(
obj.canonical || title,
{ action: 'edit', redlink: 1 }
) ||
obj.canonical && mw.util.getUrl(obj.canonical);
if (url) {
$(obj.links).attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$(obj.links).attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
static sds = new WeakMap();
static magicWords = [
'!', '=', 'BASEPAGENAME', 'BASEPAGENAME:', 'BASEPAGENAMEE', 'BASEPAGENAMEE:',
'canonicalurl:', 'CURRENTDAY', 'CURRENTDAY2', 'CURRENTDAYNAME', 'CURRENTDOW',
'CURRENTHOUR', 'CURRENTMONTH', 'CURRENTMONTH1', 'CURRENTMONTHABBREV',
'CURRENTMONTHNAME', 'CURRENTMONTHNAMEGEN', 'CURRENTTIME', 'CURRENTTIMESTAMP',
'CURRENTVERSION', 'CURRENTWEEK', 'CURRENTYEAR', 'DEFAULTCATEGORYSORT:',
'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DISPLAYTITLE:', 'filepath:', 'formatnum:',
'FULLPAGENAME', 'FULLPAGENAME:', 'FULLPAGENAMEE', 'FULLPAGENAMEE:', 'fullurl:',
'gender:', 'lc:', 'lcfirst:', 'LOCALDAY', 'LOCALDAY2', 'LOCALDAYNAME',
'LOCALDOW', 'LOCALHOUR', 'LOCALMONTH', 'LOCALMONTH1', 'LOCALMONTHABBREV',
'LOCALMONTHNAME', 'LOCALMONTHNAMEGEN', 'LOCALTIME', 'LOCALTIMESTAMP',
'LOCALWEEK', 'LOCALYEAR','NAMESPACE', 'NAMESPACE:', 'NAMESPACEE', 'NAMESPACEE:',
'NAMESPACENUMBER', 'NAMESPACENUMBER:', 'ns:', 'NUMBEROFACTIVEUSERS',
'NUMBEROFARTICLES', 'NUMBEROFEDITS', 'NUMBEROFFILES', 'NUMBEROFPAGES',
'NUMBEROFUSERS', 'padleft:', 'PAGENAME', 'PAGENAMEE', 'PAGESINCAT:',
'PAGESINCATEGORY:', 'plural:', 'REVISIONDAY', 'REVISIONDAY:', 'REVISIONDAY2',
'REVISIONDAY2:', 'REVISIONID', 'REVISIONID:', 'REVISIONMONTH', 'REVISIONMONTH:',
'REVISIONMONTH1', 'REVISIONMONTH1:', 'REVISIONSIZE', 'REVISIONTIMESTAMP',
'REVISIONTIMESTAMP:', 'REVISIONUSER', 'REVISIONUSER:', 'REVISIONYEAR',
'REVISIONYEAR:', 'ROOTPAGENAME', 'ROOTPAGENAME:', 'ROOTPAGENAMEE',
'ROOTPAGENAMEE:', 'SHORTDESC:', 'SUBJECTPAGENAME', 'SUBJECTPAGENAME:',
'SUBJECTPAGENAMEE', 'SUBJECTPAGENAMEE:', 'SUBJECTSPACE', 'SUBJECTSPACE:',
'SUBJECTSPACEE', 'SUBJECTSPACEE:', 'SUBPAGENAME', 'SUBPAGENAME:',
'SUBPAGENAMEE', 'SUBPAGENAMEE:', 'TALKPAGENAME', 'TALKPAGENAME:',
'TALKPAGENAMEE', 'TALKPAGENAMEE:', 'TALKSPACE', 'TALKSPACE:', 'TALKSPACEE',
'TALKSPACEE:', 'uc:', 'ucfirst:', 'urlencode:'
];
}
if (Array.isArray(window.smartdiffMagicWords)) {
SmartDiff.magicWords.push(...window.smartdiffMagicWords);
}
if (Array.isArray(window.smartdiffTemplates)) {
SmartDiff.templates = window.smartdiffTemplates;
}
mw.hook('wikipage.diff').add((window.sdcb = ($diff => {
window.sd = new SmartDiff($diff);
})));
mw.hook('wikipage.diff.wikitextBodyUpdate').add((window.sdcbu = ($rows => {
let $diff = $rows.closest('.diff');
let sd = SmartDiff.sds.get($diff[0]);
if (sd) {
sd.init($rows.filter(function () {
return this.checkVisibility();
}));
} else {
window.sd = new SmartDiff($diff);
}
})));
});
64mgcdktahye29qxmfbi2aeeb58ied5
769894
769843
2026-10-24T00:51:12Z
Nardog
40946
769894
javascript
text/javascript
mw.hook('wikipage.diff').remove(window.sdcb);
mw.hook('wikipage.diff.wikitextBodyUpdate').remove(window.sdcbu);
$('.smartdiff-link').contents().unwrap().end().remove();
$(':is(.diff-addedline, .diff-addedline, .diff-context) > div, .mw-diff-inline-added, .mw-diff-inline-deleted, .mw-diff-inline-changed').contents().each(function () {
if (this.previousSibling && this.previousSibling.tagName === this.tagName) {
this.previousSibling.textContent += this.textContent;
this.remove();
}
});
window.smartdiffTemplates = [
{
names: ['T', 'Tl'],
namespace: 10,
forceNs: true,
end: 1
},
{
names: ['Tlx'],
namespace: 10,
end: 1
},
{
names: ['U'],
prefix: 'Special:Contributions/',
end: 1
},
{
names: ['Re', 'Reply to', 'Ping'],
prefix: 'Special:Contributions/'
},
{
names: ['About'],
start: 3,
skipEven: true
},
{
names: ['For'],
start: 2
},
{
names: ['Other uses', 'Otheruses'],
end: 1
},
{
names: ['Section link', 'Slink'],
end: 1
},
{
names: ['Redirect'],
skipEven: true,
noRedirectEnd: 1
},
{
names: ['Shortcut'],
noRedirectStart: 1
},
{
names: ['Tracked', 'Phab'],
prefix: 'phab:',
end: 1
},
{
names: [
'Distinguish',
'Main', 'Main article',
'Further',
'See also', 'Seealso'
]
},
{
names: ['Ll'],
end: 1
}
];
mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api'
], () => {
mw.loader.addStyleTag(`body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external) {
color: #36b;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):visited {
color: #636;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):active {
color: #b63;
}`);
class SmartDiff {
constructor($diff) {
this.now = performance.now();
this.$diff = $diff;
this.isView = mw.config.get('wgNamespaceNumber') !== -1 &&
mw.config.get('wgAction') === 'view' &&
$diff.nextAll().is('.diff-hr');
[
['old', '#mw-diff-otitle1 a, #differences-prevlink'],
['new', '#mw-diff-ntitle1 a, #differences-nextlink']
].forEach(([side, selector]) => {
let link = this.$diff[0].querySelector(selector);
let pn = link && mw.util.getParamValue('title', link.search);
if (pn) {
this[side] = {
pn,
ns: mw.Title.newFromText(pn).getNamespaceId()
};
}
});
this.isSamePage = this.old?.pn === this.new?.pn;
this.titles = {};
this.noRedirLinks = new WeakSet();
SmartDiff.sds.set($diff[0], this);
this.init();
}
async init($rows = this.$diff) {
this.side = 'old';
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div);
}
this.side = 'new';
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div);
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, true);
}
let $contexts = $rows.find('.diff-context > div'), clones = [];
for (let [i, div] of $contexts.get().entries()) {
if (i % 2) {
this.side = 'new';
if (this.divHasLink) {
if (this.sidePropUsed) {
await this.processLine(div);
} else {
div.replaceChildren(
...$contexts.eq(i - 1).contents().clone()
);
clones.push(div);
}
}
} else {
this.side = 'old';
await this.processLine(div);
}
}
$(clones).find('.smartdiff-link[title]').each((_, link) => {
this.titles[link.title]?.links.push(link);
});
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, isInlineChange) {
this.divHasLink = false;
this.sidePropUsed = false;
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (isInlineChange) {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.divHasLink) return;
try {
let newDiv = this.unescape(tokens);
this.verify(div, newDiv);
div.replaceChildren(...newDiv.childNodes);
} catch (e) {
debugger;
if (typeof e === 'string') {
this.hasError = true;
console.warn(`SmartDiff error: ${e}`);
} else {
throw e;
}
}
}
tokenize(el, escSelector) {
return [...el.childNodes].flatMap(node => {
if (node.nodeType === 3) {
return node.textContent.split('').map(s => ({ s }));
}
if (node.nodeType !== 1) {
return [];
}
if (escSelector && node.matches(escSelector)) {
return [
{ openingTag: node },
{ esc: this.tokenize(node), tag: node },
{ closingTag: node }
];
}
return [
{ openingTag: node },
...this.tokenize(node, escSelector),
{ closingTag: node }
];
});
}
escapeTags(tokens, selector) {
let openTags = [], data = [];
tokens.forEach((token, i) => {
if (token.openingTag?.matches(selector)) {
openTags.push([token.openingTag, i + 1]);
} else if (token.closingTag && openTags.length &&
token.closingTag.tagName === openTags.at(-1)[0].tagName
) {
data.push([...openTags.pop(), i]);
}
});
data.reverse().forEach(([tag, start, end]) => {
let token = { tag };
token.esc = tokens.splice(start, end - start, token);
});
}
unescapeTags(tokens, selector) {
tokens.splice(0, Infinity, ...tokens.flatMap(token => (
token.esc && (!selector || token.tag.matches(selector))
? token.esc
: [token]
)));
}
unescape(tokens) {
let els = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
els.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.link) {
el = el.cloneNode();
}
els.at(-1).append(el);
els.push(el);
} else if (token.closingTag) {
let el = els.pop();
if (!els.length) {
throw `too many closing tags.\n\n${el.innerHTML}`;
}
if (el.tagName !== token.closingTag.tagName) {
throw `expected </${
token.closingTag.tagName.toLowerCase()
}>, got </${
el.tagName.toLowerCase()
}>.\n\n${els[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
els.at(-1).append(lastText);
}
if (els.length !== 1) {
throw `too many opening tags.\n\n${els[0].innerHTML}`;
}
return els[0];
}
verify(div, newDiv) {
let origHtml = div.innerHTML;
let $comp = $(newDiv).clone();
$comp.find('.smartdiff-link').contents().unwrap();
let comp = $comp.html().replace(/<\/(ins|del)><\1(?: [^>]+)?>/g, '');
if (comp !== origHtml) {
throw `changed substance.\n\nOriginal:\n${origHtml}\n\nCleaned:\n${comp}\n\nFull:\n${newDiv.innerHTML}`;
}
let emptyCells = newDiv.querySelectorAll(':is(ins, del):empty');
if (emptyCells.length) {
console.log('Empty cells found:', ...emptyCells);
mw.notify('Empty cells found');
}
}
async exec(tokens, isSecond) {
let s = tokens.map(token => token.s || '').join('');
let modes = isSecond
? ['url', 'link', 'template', 'gallery']
: ['url', 'link', 'template', 'heading', 'gallery'];
for (let mode of modes) {
await this.search(s, tokens, mode);
}
}
async search(s, tokens, mode, data) {
for (let match of s.matchAll(SmartDiff[mode + 'Re'])) {
await this[mode + 'Callback'](
tokens,
...match.indices.slice(1).map((indices, i) => indices && {
s: match[i + 1],
indices,
tokens,
tokenIndices: SmartDiff.tokenIndices
}),
data
);
}
}
static tokenIndices() {
let count = 0, start;
for (let [i, token] of this.tokens.entries()) {
if (token.s) {
if (count === this.indices[0]) {
start = i;
}
if (count === this.indices[1] - 1) {
return [start, i + 1];
}
count++;
}
}
}
static linkRe = /\[\[\s*(?:<tvar(?: [^>]*)?>\s*)?((?:[^<>\[\]{|}])+?)(?:\s*<\/tvar>|\{\{#translation:\s*\}\})?\s*(?:\||\]\])/dg;
async linkCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (s.startsWith('/')) {
t = await this.getSubpage(s);
} else if (s.startsWith('#') && (!this.isView || !this.isSamePage)) {
t = mw.Title.newFromText(this.getProp() + s);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s);
} else {
t = mw.Title.newFromText(s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
static templateRe = /((?<!{){{\s*(?:(?:#invoke|(?:safe)?subst|msg(?:nw)?|raw|int):\s*)?(?:<tvar(?: [^>]*)?>\s*)?)([^<>\[\]{|}]+?)((?:\s*<\/tvar>)?\s*(\|.*)?(?:}}|$))/dg;
async templateCallback(tokens, pre, mid, post, args) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (pre.s.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} else if (s.startsWith('#')) {
return;
} else if (pre.s.includes('int')) {
t = mw.Title.makeTitle(8, s);
} else if (s.startsWith('/')) {
t = await this.getSubpage(s, 10);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s, 10);
} else {
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
if (match && SmartDiff.magicWords.includes(match[0])) return;
}
t = mw.Title.newFromText(s, 10);
}
if (t) {
this.link(tokens, ...mid.tokenIndices(), t);
}
if (!args) return;
let [postStart, postEnd] = post.tokenIndices();
let newPost = tokens.slice(postStart, postEnd);
await this.search(post.s, newPost, 'template');
if (!t || !SmartDiff.templates) return;
let divHasLink = this.divHasLink;
this.divHasLink = false;
let name = t.getMainText();
for (let data of SmartDiff.templates) {
if (Array.isArray(data?.names) &&
Array.prototype.includes.call(data.names, name)
) {
await this.search(
post.s,
newPost,
'templateArgs',
{ ...data, i: 1 }
);
}
}
if (this.divHasLink) {
tokens.splice(postStart, postEnd - postStart, ...newPost);
} else {
this.divHasLink = divHasLink;
}
}
static templateArgsRe = /\|\s*(?:([^=|]*?)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, name, mid, data) {
let n = name ? Number(name.s) || name.s : data.i++;
if (typeof n !== 'number' || (
n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven)
)) {
return;
}
if (!mid.s) return;
let s = this.decodeWikitext(mid.s);
if (data.prefix) {
s = data.prefix + s;
}
if (data.suffix) {
s += data.suffix;
}
let t = data.forceNs
? mw.Title.makeTitle(data.namespace, s)
: mw.Title.newFromText(s, data.namespace);
if (!t) return;
this.link(
tokens,
...mid.tokenIndices(),
t,
n >= data.noRedirectStart || n <= data.noRedirectEnd
);
}
static urlRe = /(\[(?=.+\]))?((?:\bhttps?:)?\/\/[^\s"<>\[\]{|}]*)([^\s"<>\[\]{|}])/dg;
urlCallback(tokens, pre, mid, last) {
let omitLast;
if (!pre) {
if (mid.s.startsWith('//')) return;
if (last.s === ';') {
let match = mid.s.match(/&#?[\da-z]+$/i);
if (match) {
let source = match[0] + last.s;
omitLast = this.decodeWikitext(source) === source;
} else {
omitLast = true;
}
} else {
omitLast = '!,.:?'.includes(last.s) ||
last.s === ')' && mid.s.includes('(');
}
}
let s = mid.s;
if (!omitLast) {
s += last.s;
}
let [midStart, midEnd] = mid.tokenIndices();
this.link(
tokens,
midStart,
omitLast ? midEnd : last.tokenIndices()[1],
this.decodeWikitext(s)
);
}
static headingRe = /^(={1,6})\s*(.+?)\s*(\1)\s*(?=<!--|$)/dg;
headingCallback(tokens, pre, mid, post) {
if (/{{|<\/?ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s\[\]]+\]/i.test(mid.s)) return;
let s = this.decodeWikitext(mid.s).replace(
/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|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').replace(/^[\s_]+|[\s_]+$/g, '');
let t = mw.Title.newFromText(
`${!this.isView || !this.isSamePage ? this.getProp() : ''}#${s}`
);
if (!t) return;
let midIndices = mid.tokenIndices();
if (tokens.slice(...midIndices).some(({ link }) => link)) {
this.link(tokens, ...post.tokenIndices(), t);
this.link(tokens, ...pre.tokenIndices(), t);
} else {
this.link(tokens, ...midIndices, t);
}
}
// https://commons.wikimedia.org/wiki/Special:MediaStatistics
static galleryRe = /^\s*((?:[^<>\[\]{|}])+\.(?:apng|djv|djvu|flac|gif|jpe|jpeg|jpg|jps|kar|m4a|m4b|m4p|m4r|m4v|mid|midi|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpga|oga|ogg|ogm|ogv|ogx|opus|pdf|png|spx|stl|svg|tif|tiff|wav|webm|webp|xcf))\s*(?:\||$)/dgi;
galleryCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t = mw.Title.newFromText(s, 6);
if (t && t.getNamespaceId() !== 6) {
t = mw.Title.makeTitle(6, s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
link(tokens, start, end, mwtOrUrl, noRedirect) {
let indices = [], last;
tokens.slice(start, end).forEach((token, i) => {
if (!token.s && !token.link && !token.esc) return;
if (token.s && !token.link) {
if (!indices.length || last?.link || last?.esc) {
indices.push([start + i]);
}
indices.at(-1)[1] = start + i + 1;
}
last = token;
});
if (indices.some(chunkIndices => chunkIndices.length !== 2)) {
mw.notify('Corrupt chunks found');
debugger;
}
if (!indices.length) return;
let attribs = { class: 'smartdiff-link' }, obj;
if (typeof mwtOrUrl === 'string') {
attribs.class += ' external';
attribs.href = mwtOrUrl;
attribs.rel = 'nofollow';
} else {
let title = mwtOrUrl.toText();
if (title) {
if (Object.hasOwn(this.titles, title)) {
obj = this.titles[title];
if (obj.classes) {
attribs.class += ' ' + obj.classes.join(' ');
}
} else {
obj = { links: [] };
this.titles[title] = obj;
}
let fragment;
if (obj.url) {
attribs.href = obj.url;
fragment = mwtOrUrl.getFragment();
} else {
let params = obj.red && { action: 'edit', redlink: 1 } ||
noRedirect && obj.redirect && { redirect: 'no' };
if (obj.canonical) {
attribs.href = mw.util.getUrl(obj.canonical, params);
fragment = mwtOrUrl.getFragment();
} else {
attribs.href = mwtOrUrl.getUrl(params);
}
}
if (fragment) {
attribs.href += mw.util.getUrl('#' + fragment);
}
attribs.title = obj.canonical || title;
} else {
attribs.class += ' mw-selflink-fragment';
attribs.href = mwtOrUrl.getUrl();
}
}
let links = indices.reverse().map(chunkIndices => (
this.linkChunk(tokens, ...chunkIndices, attribs)
));
if (obj && !obj.processed) {
obj.links.push(...links);
if (noRedirect) {
links.forEach(link => {
this.noRedirLinks.add(link);
});
}
}
this.divHasLink = true;
}
linkChunk(tokens, start, end, attribs) {
let newStart = start, newEnd = end;
let mid = tokens.slice(start, end);
let midTags = mid.filter(token => token.openingTag || token.closingTag);
for (let i = 0; ; i += 2) {
if (tokens[newStart - 1]?.openingTag && midTags[i]?.closingTag &&
tokens[newStart - 1].openingTag.tagName === midTags[i].closingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
} else {
break;
}
}
for (let i = midTags.length - 1; ; i--) {
if (tokens[newEnd]?.closingTag && midTags[i]?.openingTag &&
tokens[newEnd].closingTag.tagName === midTags[i].openingTag.tagName
) {
midTags.push(tokens[newEnd]);
newEnd++;
} else {
break;
}
}
if (start - newStart) {
console.log(`Moved up ${start - newStart} times`);
}
if (start - newStart > 1) {
mw.notify(`Moved up ${start - newStart} times`);
}
if (newEnd - end) {
console.log(`Moved down ${newEnd - end} times`);
}
if (newEnd - end > 1) {
mw.notify(`Moved down ${newEnd - end} times`);
}
let link = $('<a>').attr(attribs)[0];
let pre = [{ openingTag: link, link }];
let post = [{ closingTag: link, link }];
if (midTags[0]?.closingTag) {
pre.unshift({ ...midTags[0] });
pre.push({ openingTag: midTags[0].closingTag });
}
if (midTags.at(-1)?.openingTag) {
post.unshift({ closingTag: midTags.at(-1).openingTag });
post.push({ ...midTags.at(-1) });
}
tokens.splice(newEnd, 0, ...post);
tokens.splice(newStart, 0, ...pre);
mid.forEach(token => {
if (token.s) {
token.link = link;
}
});
return link;
}
decodeWikitext(text, decodePercents) {
let s = text;
if (decodePercents) {
try {
s = decodeURIComponent(s);
} catch {}
}
return $($.parseHTML(s)).text();
}
async getSubpage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
return mw.Title.newFromText(
this.getProp() + s.replace(/\/+$/, '')
);
}
return mw.Title.newFromText(s, fallbackNs);
}
async getParentPage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
let chunks = s.split('/');
let levelCount = chunks.findIndex(v => v !== '..');
let sup = this.getProp().split('/').slice(0, -levelCount).join('/');
if (sup) {
let sub = chunks.slice(levelCount).join('/').replace(/\/+$/, '');
return mw.Title.newFromText(sub ? `${sup}/${sub}` : sup);
}
}
return mw.Title.newFromText(s, fallbackNs);
}
async canHaveSubpages(ns = this.getProp('ns')) {
SmartDiff.subNs = SmartDiff.subNs ||
mw.config.get('wgVisualEditorConfig')?.namespacesWithSubpages ||
Object.values((await new mw.Api().get({
action: 'query',
meta: 'siteinfo',
siprop: 'namespaces',
formatversion: 2
})).query.namespaces).filter(n => n.subpages).map(n => n.id);
return SmartDiff.subNs.includes(ns);
}
getProp(n = 'pn') {
if (this[this.side]) {
this.sidePropUsed = !this.isSamePage;
return this[this.side][n];
}
if (Object.hasOwn(this, n)) {
return this[n];
}
this.pn = mw.config.get('wgPageName');
this.ns = mw.config.get('wgNamespaceNumber');
return this[n];
}
async queryTitles() {
let unprocessed = Object.entries(this.titles)
.filter(([, v]) => !v.processed).slice(0, 500).map(([k]) => k);
while (unprocessed.length) {
let curTitles = unprocessed.slice(0, 50);
curTitles.forEach(title => {
this.titles[title].processed = true;
});
let { query } = await new mw.Api().post({
action: 'query',
titles: curTitles,
iwurl: 1,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: this.getProp(),
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.pages?.forEach(p => {
let obj = (this.titles[p.title] ??= {});
let classes = p.linkclasses || [];
if (p.missing && !p.known) {
classes.push('new');
obj.red = true;
} else if (p.redirect) {
obj.redirect = true;
}
if (classes.length) {
obj.classes = classes;
}
});
query.interwiki?.forEach(iw => {
this.titles[iw.title] = { classes: ['extiw'], url: iw.url };
});
query.normalized?.forEach(({ from, to }) => {
this.titles[from] = {
...this.titles[from],
...this.titles[to],
canonical: to
};
});
curTitles.forEach(title => {
let obj = this.titles[title];
if (obj.classes) {
$(obj.links).addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$(obj.links).attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
noRedirLinks.has(this) && { redirect: 'no' }
) + this.hash;
});
} else {
let url = obj.url ||
obj.red && mw.util.getUrl(
obj.canonical || title,
{ action: 'edit', redlink: 1 }
) ||
obj.canonical && mw.util.getUrl(obj.canonical);
if (url) {
$(obj.links).attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$(obj.links).attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
static sds = new WeakMap();
static magicWords = [
'!', '=', 'BASEPAGENAME', 'BASEPAGENAME:', 'BASEPAGENAMEE', 'BASEPAGENAMEE:',
'canonicalurl:', 'CURRENTDAY', 'CURRENTDAY2', 'CURRENTDAYNAME', 'CURRENTDOW',
'CURRENTHOUR', 'CURRENTMONTH', 'CURRENTMONTH1', 'CURRENTMONTHABBREV',
'CURRENTMONTHNAME', 'CURRENTMONTHNAMEGEN', 'CURRENTTIME', 'CURRENTTIMESTAMP',
'CURRENTVERSION', 'CURRENTWEEK', 'CURRENTYEAR', 'DEFAULTCATEGORYSORT:',
'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DISPLAYTITLE:', 'filepath:', 'formatnum:',
'FULLPAGENAME', 'FULLPAGENAME:', 'FULLPAGENAMEE', 'FULLPAGENAMEE:', 'fullurl:',
'gender:', 'lc:', 'lcfirst:', 'LOCALDAY', 'LOCALDAY2', 'LOCALDAYNAME',
'LOCALDOW', 'LOCALHOUR', 'LOCALMONTH', 'LOCALMONTH1', 'LOCALMONTHABBREV',
'LOCALMONTHNAME', 'LOCALMONTHNAMEGEN', 'LOCALTIME', 'LOCALTIMESTAMP',
'LOCALWEEK', 'LOCALYEAR','NAMESPACE', 'NAMESPACE:', 'NAMESPACEE', 'NAMESPACEE:',
'NAMESPACENUMBER', 'NAMESPACENUMBER:', 'ns:', 'NUMBEROFACTIVEUSERS',
'NUMBEROFARTICLES', 'NUMBEROFEDITS', 'NUMBEROFFILES', 'NUMBEROFPAGES',
'NUMBEROFUSERS', 'padleft:', 'PAGENAME', 'PAGENAMEE', 'PAGESINCAT:',
'PAGESINCATEGORY:', 'plural:', 'REVISIONDAY', 'REVISIONDAY:', 'REVISIONDAY2',
'REVISIONDAY2:', 'REVISIONID', 'REVISIONID:', 'REVISIONMONTH', 'REVISIONMONTH:',
'REVISIONMONTH1', 'REVISIONMONTH1:', 'REVISIONSIZE', 'REVISIONTIMESTAMP',
'REVISIONTIMESTAMP:', 'REVISIONUSER', 'REVISIONUSER:', 'REVISIONYEAR',
'REVISIONYEAR:', 'ROOTPAGENAME', 'ROOTPAGENAME:', 'ROOTPAGENAMEE',
'ROOTPAGENAMEE:', 'SHORTDESC:', 'SUBJECTPAGENAME', 'SUBJECTPAGENAME:',
'SUBJECTPAGENAMEE', 'SUBJECTPAGENAMEE:', 'SUBJECTSPACE', 'SUBJECTSPACE:',
'SUBJECTSPACEE', 'SUBJECTSPACEE:', 'SUBPAGENAME', 'SUBPAGENAME:',
'SUBPAGENAMEE', 'SUBPAGENAMEE:', 'TALKPAGENAME', 'TALKPAGENAME:',
'TALKPAGENAMEE', 'TALKPAGENAMEE:', 'TALKSPACE', 'TALKSPACE:', 'TALKSPACEE',
'TALKSPACEE:', 'uc:', 'ucfirst:', 'urlencode:'
];
}
if (Array.isArray(window.smartdiffMagicWords)) {
SmartDiff.magicWords.push(...window.smartdiffMagicWords);
}
if (Array.isArray(window.smartdiffTemplates)) {
SmartDiff.templates = window.smartdiffTemplates;
}
mw.hook('wikipage.diff').add((window.sdcb = ($diff => {
window.sd = new SmartDiff($diff);
})));
mw.hook('wikipage.diff.wikitextBodyUpdate').add((window.sdcbu = ($rows => {
let $diff = $rows.closest('.diff');
let sd = SmartDiff.sds.get($diff[0]);
if (sd) {
sd.init($rows.filter(function () {
return this.checkVisibility();
}));
} else {
window.sd = new SmartDiff($diff);
}
})));
});
t4pbk721w4kd1xtkel4j6rj69k2hoce
769895
769894
2026-10-24T00:52:32Z
Nardog
40946
769895
javascript
text/javascript
mw.hook('wikipage.diff').remove(window.sdcb);
mw.hook('wikipage.diff.wikitextBodyUpdate').remove(window.sdcbu);
$('.smartdiff-link').contents().unwrap().end().remove();
$(':is(.diff-addedline, .diff-addedline, .diff-context) > div, .mw-diff-inline-added, .mw-diff-inline-deleted, .mw-diff-inline-changed').contents().each(function () {
if (this.previousSibling && this.previousSibling.tagName === this.tagName) {
this.previousSibling.textContent += this.textContent;
this.remove();
}
});
window.smartdiffTemplates = [
{
names: ['T', 'Tl'],
namespace: 10,
forceNs: true,
end: 1
},
{
names: ['Tlx'],
namespace: 10,
end: 1
},
{
names: ['U'],
prefix: 'Special:Contributions/',
end: 1
},
{
names: ['Re', 'Reply to', 'Ping'],
prefix: 'Special:Contributions/'
},
{
names: ['About'],
start: 3,
skipEven: true
},
{
names: ['For'],
start: 2
},
{
names: ['Other uses', 'Otheruses'],
end: 1
},
{
names: ['Section link', 'Slink'],
end: 1
},
{
names: ['Redirect'],
skipEven: true,
noRedirectEnd: 1
},
{
names: ['Shortcut'],
noRedirectStart: 1
},
{
names: ['Tracked', 'Phab'],
prefix: 'phab:',
end: 1
},
{
names: [
'Distinguish',
'Main', 'Main article',
'Further',
'See also', 'Seealso'
]
},
{
names: ['Ll'],
end: 1
}
];
mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api'
], () => {
mw.loader.addStyleTag(`body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external) {
color: #36b;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):visited {
color: #636;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):active {
color: #b63;
}`);
class SmartDiff {
constructor($diff) {
this.now = performance.now();
this.$diff = $diff;
this.isView = mw.config.get('wgNamespaceNumber') !== -1 &&
mw.config.get('wgAction') === 'view' &&
$diff.nextAll().is('.diff-hr');
[
['old', '#mw-diff-otitle1 a, #differences-prevlink'],
['new', '#mw-diff-ntitle1 a, #differences-nextlink']
].forEach(([side, selector]) => {
let link = this.$diff[0].querySelector(selector);
let pn = link && mw.util.getParamValue('title', link.search);
if (pn) {
this[side] = {
pn,
ns: mw.Title.newFromText(pn).getNamespaceId()
};
}
});
this.isSamePage = this.old?.pn === this.new?.pn;
this.titles = {};
this.noRedirLinks = new WeakSet();
SmartDiff.sds.set($diff[0], this);
this.init();
}
async init($rows = this.$diff) {
this.side = 'old';
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div);
}
this.side = 'new';
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div);
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, true);
}
let $contexts = $rows.find('.diff-context > div'), clones = [];
for (let [i, div] of $contexts.get().entries()) {
if (i % 2) {
this.side = 'new';
if (this.divHasLink) {
if (this.sidePropUsed) {
await this.processLine(div);
} else {
div.replaceChildren(
...$contexts.eq(i - 1).contents().clone()
);
clones.push(div);
}
}
} else {
this.side = 'old';
await this.processLine(div);
}
}
$(clones).find('.smartdiff-link[title]').each((_, link) => {
this.titles[link.title]?.links.push(link);
});
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, isInlineChange) {
this.divHasLink = false;
this.sidePropUsed = false;
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (isInlineChange) {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.divHasLink) return;
try {
let newDiv = this.unescape(tokens);
this.verify(div, newDiv);
div.replaceChildren(...newDiv.childNodes);
} catch (e) {
debugger;
if (typeof e === 'string') {
this.hasError = true;
console.warn(`SmartDiff error: ${e}`);
} else {
throw e;
}
}
}
tokenize(el, escSelector) {
return [...el.childNodes].flatMap(node => {
if (node.nodeType === 3) {
return node.textContent.split('').map(s => ({ s }));
}
if (node.nodeType !== 1) {
return [];
}
if (escSelector && node.matches(escSelector)) {
return [
{ openingTag: node },
{ esc: this.tokenize(node), tag: node },
{ closingTag: node }
];
}
return [
{ openingTag: node },
...this.tokenize(node, escSelector),
{ closingTag: node }
];
});
}
escapeTags(tokens, selector) {
let openTags = [], data = [];
tokens.forEach((token, i) => {
if (token.openingTag?.matches(selector)) {
openTags.push([token.openingTag, i + 1]);
} else if (token.closingTag && openTags.length &&
token.closingTag.tagName === openTags.at(-1)[0].tagName
) {
data.push([...openTags.pop(), i]);
}
});
data.reverse().forEach(([tag, start, end]) => {
let token = { tag };
token.esc = tokens.splice(start, end - start, token);
});
}
unescapeTags(tokens, selector) {
tokens.splice(0, Infinity, ...tokens.flatMap(token => (
token.esc && (!selector || token.tag.matches(selector))
? token.esc
: [token]
)));
}
unescape(tokens) {
let els = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
els.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.link) {
el = el.cloneNode();
}
els.at(-1).append(el);
els.push(el);
} else if (token.closingTag) {
let el = els.pop();
if (!els.length) {
throw `too many closing tags.\n\n${el.innerHTML}`;
}
if (el.tagName !== token.closingTag.tagName) {
throw `expected </${
token.closingTag.tagName.toLowerCase()
}>, got </${
el.tagName.toLowerCase()
}>.\n\n${els[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
els.at(-1).append(lastText);
}
if (els.length !== 1) {
throw `too many opening tags.\n\n${els[0].innerHTML}`;
}
return els[0];
}
verify(div, newDiv) {
let origHtml = div.innerHTML;
let $comp = $(newDiv).clone();
$comp.find('.smartdiff-link').contents().unwrap();
let comp = $comp.html().replace(/<\/(ins|del)><\1(?: [^>]+)?>/g, '');
if (comp !== origHtml) {
throw `changed substance.\n\nOriginal:\n${origHtml}\n\nCleaned:\n${comp}\n\nFull:\n${newDiv.innerHTML}`;
}
let emptyCells = newDiv.querySelectorAll(':is(ins, del):empty');
if (emptyCells.length) {
console.log('Empty cells found:', ...emptyCells);
mw.notify('Empty cells found');
}
}
async exec(tokens, isSecond) {
let s = tokens.map(token => token.s || '').join('');
let modes = isSecond
? ['url', 'link', 'template', 'gallery']
: ['url', 'link', 'template', 'heading', 'gallery'];
for (let mode of modes) {
await this.search(s, tokens, mode);
}
}
async search(s, tokens, mode, data) {
for (let match of s.matchAll(SmartDiff[mode + 'Re'])) {
await this[mode + 'Callback'](
tokens,
...match.indices.slice(1).map((indices, i) => indices && {
s: match[i + 1],
indices,
tokens,
tokenIndices: SmartDiff.tokenIndices
}),
data
);
}
}
static tokenIndices() {
let count = 0, start;
for (let [i, token] of this.tokens.entries()) {
if (token.s) {
if (count === this.indices[0]) {
start = i;
}
if (count === this.indices[1] - 1) {
return [start, i + 1];
}
count++;
}
}
}
static linkRe = /\[\[\s*(?:<tvar(?: [^>]*)?>\s*)?((?:[^<>\[\]{|}])+?)(?:\s*<\/tvar>|\{\{#translation:\s*\}\})?\s*(?:\||\]\])/dg;
async linkCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (s.startsWith('/')) {
t = await this.getSubpage(s);
} else if (s.startsWith('#') && (!this.isView || !this.isSamePage)) {
t = mw.Title.newFromText(this.getProp() + s);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s);
} else {
t = mw.Title.newFromText(s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
static templateRe = /((?<!{){{\s*(?:(?:#invoke|(?:safe)?subst|msg(?:nw)?|raw|int):\s*)?(?:<tvar(?: [^>]*)?>\s*)?)([^<>\[\]{|}]+?)((?:\s*<\/tvar>)?\s*(\|.*)?(?:}}|$))/dg;
async templateCallback(tokens, pre, mid, post, args) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (pre.s.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} else if (s.startsWith('#')) {
return;
} else if (pre.s.includes('int')) {
t = mw.Title.makeTitle(8, s);
} else if (s.startsWith('/')) {
t = await this.getSubpage(s, 10);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s, 10);
} else {
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
if (match && SmartDiff.magicWords.includes(match[0])) return;
}
t = mw.Title.newFromText(s, 10);
}
if (t) {
this.link(tokens, ...mid.tokenIndices(), t);
}
if (!args) return;
let [postStart, postEnd] = post.tokenIndices();
let newPost = tokens.slice(postStart, postEnd);
await this.search(post.s, newPost, 'template');
if (!t || !SmartDiff.templates) return;
let divHadLink = this.divHasLink;
this.divHasLink = false;
let name = t.getMainText();
for (let data of SmartDiff.templates) {
if (Array.isArray(data?.names) &&
Array.prototype.includes.call(data.names, name)
) {
await this.search(
post.s,
newPost,
'templateArgs',
{ ...data, i: 1 }
);
}
}
if (this.divHasLink) {
tokens.splice(postStart, postEnd - postStart, ...newPost);
} else {
this.divHasLink = divHadLink;
}
}
static templateArgsRe = /\|\s*(?:([^=|]*?)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, name, mid, data) {
let n = name ? Number(name.s) || name.s : data.i++;
if (typeof n !== 'number' || (
n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven)
)) {
return;
}
if (!mid.s) return;
let s = this.decodeWikitext(mid.s);
if (data.prefix) {
s = data.prefix + s;
}
if (data.suffix) {
s += data.suffix;
}
let t = data.forceNs
? mw.Title.makeTitle(data.namespace, s)
: mw.Title.newFromText(s, data.namespace);
if (!t) return;
this.link(
tokens,
...mid.tokenIndices(),
t,
n >= data.noRedirectStart || n <= data.noRedirectEnd
);
}
static urlRe = /(\[(?=.+\]))?((?:\bhttps?:)?\/\/[^\s"<>\[\]{|}]*)([^\s"<>\[\]{|}])/dg;
urlCallback(tokens, pre, mid, last) {
let omitLast;
if (!pre) {
if (mid.s.startsWith('//')) return;
if (last.s === ';') {
let match = mid.s.match(/&#?[\da-z]+$/i);
if (match) {
let source = match[0] + last.s;
omitLast = this.decodeWikitext(source) === source;
} else {
omitLast = true;
}
} else {
omitLast = '!,.:?'.includes(last.s) ||
last.s === ')' && mid.s.includes('(');
}
}
let s = mid.s;
if (!omitLast) {
s += last.s;
}
let [midStart, midEnd] = mid.tokenIndices();
this.link(
tokens,
midStart,
omitLast ? midEnd : last.tokenIndices()[1],
this.decodeWikitext(s)
);
}
static headingRe = /^(={1,6})\s*(.+?)\s*(\1)\s*(?=<!--|$)/dg;
headingCallback(tokens, pre, mid, post) {
if (/{{|<\/?ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s\[\]]+\]/i.test(mid.s)) return;
let s = this.decodeWikitext(mid.s).replace(
/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|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').replace(/^[\s_]+|[\s_]+$/g, '');
let t = mw.Title.newFromText(
`${!this.isView || !this.isSamePage ? this.getProp() : ''}#${s}`
);
if (!t) return;
let midIndices = mid.tokenIndices();
if (tokens.slice(...midIndices).some(({ link }) => link)) {
this.link(tokens, ...post.tokenIndices(), t);
this.link(tokens, ...pre.tokenIndices(), t);
} else {
this.link(tokens, ...midIndices, t);
}
}
// https://commons.wikimedia.org/wiki/Special:MediaStatistics
static galleryRe = /^\s*((?:[^<>\[\]{|}])+\.(?:apng|djv|djvu|flac|gif|jpe|jpeg|jpg|jps|kar|m4a|m4b|m4p|m4r|m4v|mid|midi|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpga|oga|ogg|ogm|ogv|ogx|opus|pdf|png|spx|stl|svg|tif|tiff|wav|webm|webp|xcf))\s*(?:\||$)/dgi;
galleryCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t = mw.Title.newFromText(s, 6);
if (t && t.getNamespaceId() !== 6) {
t = mw.Title.makeTitle(6, s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
link(tokens, start, end, mwtOrUrl, noRedirect) {
let indices = [], last;
tokens.slice(start, end).forEach((token, i) => {
if (!token.s && !token.link && !token.esc) return;
if (token.s && !token.link) {
if (!indices.length || last?.link || last?.esc) {
indices.push([start + i]);
}
indices.at(-1)[1] = start + i + 1;
}
last = token;
});
if (indices.some(chunkIndices => chunkIndices.length !== 2)) {
mw.notify('Corrupt chunks found');
debugger;
}
if (!indices.length) return;
let attribs = { class: 'smartdiff-link' }, obj;
if (typeof mwtOrUrl === 'string') {
attribs.class += ' external';
attribs.href = mwtOrUrl;
attribs.rel = 'nofollow';
} else {
let title = mwtOrUrl.toText();
if (title) {
if (Object.hasOwn(this.titles, title)) {
obj = this.titles[title];
if (obj.classes) {
attribs.class += ' ' + obj.classes.join(' ');
}
} else {
obj = { links: [] };
this.titles[title] = obj;
}
let fragment;
if (obj.url) {
attribs.href = obj.url;
fragment = mwtOrUrl.getFragment();
} else {
let params = obj.red && { action: 'edit', redlink: 1 } ||
noRedirect && obj.redirect && { redirect: 'no' };
if (obj.canonical) {
attribs.href = mw.util.getUrl(obj.canonical, params);
fragment = mwtOrUrl.getFragment();
} else {
attribs.href = mwtOrUrl.getUrl(params);
}
}
if (fragment) {
attribs.href += mw.util.getUrl('#' + fragment);
}
attribs.title = obj.canonical || title;
} else {
attribs.class += ' mw-selflink-fragment';
attribs.href = mwtOrUrl.getUrl();
}
}
let links = indices.reverse().map(chunkIndices => (
this.linkChunk(tokens, ...chunkIndices, attribs)
));
if (obj && !obj.processed) {
obj.links.push(...links);
if (noRedirect) {
links.forEach(link => {
this.noRedirLinks.add(link);
});
}
}
this.divHasLink = true;
}
linkChunk(tokens, start, end, attribs) {
let newStart = start, newEnd = end;
let mid = tokens.slice(start, end);
let midTags = mid.filter(token => token.openingTag || token.closingTag);
for (let i = 0; ; i += 2) {
if (tokens[newStart - 1]?.openingTag && midTags[i]?.closingTag &&
tokens[newStart - 1].openingTag.tagName === midTags[i].closingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
} else {
break;
}
}
for (let i = midTags.length - 1; ; i--) {
if (tokens[newEnd]?.closingTag && midTags[i]?.openingTag &&
tokens[newEnd].closingTag.tagName === midTags[i].openingTag.tagName
) {
midTags.push(tokens[newEnd]);
newEnd++;
} else {
break;
}
}
if (start - newStart) {
console.log(`Moved up ${start - newStart} times`);
}
if (start - newStart > 1) {
mw.notify(`Moved up ${start - newStart} times`);
}
if (newEnd - end) {
console.log(`Moved down ${newEnd - end} times`);
}
if (newEnd - end > 1) {
mw.notify(`Moved down ${newEnd - end} times`);
}
let link = $('<a>').attr(attribs)[0];
let pre = [{ openingTag: link, link }];
let post = [{ closingTag: link, link }];
if (midTags[0]?.closingTag) {
pre.unshift({ ...midTags[0] });
pre.push({ openingTag: midTags[0].closingTag });
}
if (midTags.at(-1)?.openingTag) {
post.unshift({ closingTag: midTags.at(-1).openingTag });
post.push({ ...midTags.at(-1) });
}
tokens.splice(newEnd, 0, ...post);
tokens.splice(newStart, 0, ...pre);
mid.forEach(token => {
if (token.s) {
token.link = link;
}
});
return link;
}
decodeWikitext(text, decodePercents) {
let s = text;
if (decodePercents) {
try {
s = decodeURIComponent(s);
} catch {}
}
return $($.parseHTML(s)).text();
}
async getSubpage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
return mw.Title.newFromText(
this.getProp() + s.replace(/\/+$/, '')
);
}
return mw.Title.newFromText(s, fallbackNs);
}
async getParentPage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
let chunks = s.split('/');
let levelCount = chunks.findIndex(v => v !== '..');
let sup = this.getProp().split('/').slice(0, -levelCount).join('/');
if (sup) {
let sub = chunks.slice(levelCount).join('/').replace(/\/+$/, '');
return mw.Title.newFromText(sub ? `${sup}/${sub}` : sup);
}
}
return mw.Title.newFromText(s, fallbackNs);
}
async canHaveSubpages(ns = this.getProp('ns')) {
SmartDiff.subNs = SmartDiff.subNs ||
mw.config.get('wgVisualEditorConfig')?.namespacesWithSubpages ||
Object.values((await new mw.Api().get({
action: 'query',
meta: 'siteinfo',
siprop: 'namespaces',
formatversion: 2
})).query.namespaces).filter(n => n.subpages).map(n => n.id);
return SmartDiff.subNs.includes(ns);
}
getProp(n = 'pn') {
if (this[this.side]) {
this.sidePropUsed = !this.isSamePage;
return this[this.side][n];
}
if (Object.hasOwn(this, n)) {
return this[n];
}
this.pn = mw.config.get('wgPageName');
this.ns = mw.config.get('wgNamespaceNumber');
return this[n];
}
async queryTitles() {
let unprocessed = Object.entries(this.titles)
.filter(([, v]) => !v.processed).slice(0, 500).map(([k]) => k);
while (unprocessed.length) {
let curTitles = unprocessed.slice(0, 50);
curTitles.forEach(title => {
this.titles[title].processed = true;
});
let { query } = await new mw.Api().post({
action: 'query',
titles: curTitles,
iwurl: 1,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: this.getProp(),
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.pages?.forEach(p => {
let obj = (this.titles[p.title] ??= {});
let classes = p.linkclasses || [];
if (p.missing && !p.known) {
classes.push('new');
obj.red = true;
} else if (p.redirect) {
obj.redirect = true;
}
if (classes.length) {
obj.classes = classes;
}
});
query.interwiki?.forEach(iw => {
this.titles[iw.title] = { classes: ['extiw'], url: iw.url };
});
query.normalized?.forEach(({ from, to }) => {
this.titles[from] = {
...this.titles[from],
...this.titles[to],
canonical: to
};
});
curTitles.forEach(title => {
let obj = this.titles[title];
if (obj.classes) {
$(obj.links).addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$(obj.links).attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
noRedirLinks.has(this) && { redirect: 'no' }
) + this.hash;
});
} else {
let url = obj.url ||
obj.red && mw.util.getUrl(
obj.canonical || title,
{ action: 'edit', redlink: 1 }
) ||
obj.canonical && mw.util.getUrl(obj.canonical);
if (url) {
$(obj.links).attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$(obj.links).attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
static sds = new WeakMap();
static magicWords = [
'!', '=', 'BASEPAGENAME', 'BASEPAGENAME:', 'BASEPAGENAMEE', 'BASEPAGENAMEE:',
'canonicalurl:', 'CURRENTDAY', 'CURRENTDAY2', 'CURRENTDAYNAME', 'CURRENTDOW',
'CURRENTHOUR', 'CURRENTMONTH', 'CURRENTMONTH1', 'CURRENTMONTHABBREV',
'CURRENTMONTHNAME', 'CURRENTMONTHNAMEGEN', 'CURRENTTIME', 'CURRENTTIMESTAMP',
'CURRENTVERSION', 'CURRENTWEEK', 'CURRENTYEAR', 'DEFAULTCATEGORYSORT:',
'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DISPLAYTITLE:', 'filepath:', 'formatnum:',
'FULLPAGENAME', 'FULLPAGENAME:', 'FULLPAGENAMEE', 'FULLPAGENAMEE:', 'fullurl:',
'gender:', 'lc:', 'lcfirst:', 'LOCALDAY', 'LOCALDAY2', 'LOCALDAYNAME',
'LOCALDOW', 'LOCALHOUR', 'LOCALMONTH', 'LOCALMONTH1', 'LOCALMONTHABBREV',
'LOCALMONTHNAME', 'LOCALMONTHNAMEGEN', 'LOCALTIME', 'LOCALTIMESTAMP',
'LOCALWEEK', 'LOCALYEAR','NAMESPACE', 'NAMESPACE:', 'NAMESPACEE', 'NAMESPACEE:',
'NAMESPACENUMBER', 'NAMESPACENUMBER:', 'ns:', 'NUMBEROFACTIVEUSERS',
'NUMBEROFARTICLES', 'NUMBEROFEDITS', 'NUMBEROFFILES', 'NUMBEROFPAGES',
'NUMBEROFUSERS', 'padleft:', 'PAGENAME', 'PAGENAMEE', 'PAGESINCAT:',
'PAGESINCATEGORY:', 'plural:', 'REVISIONDAY', 'REVISIONDAY:', 'REVISIONDAY2',
'REVISIONDAY2:', 'REVISIONID', 'REVISIONID:', 'REVISIONMONTH', 'REVISIONMONTH:',
'REVISIONMONTH1', 'REVISIONMONTH1:', 'REVISIONSIZE', 'REVISIONTIMESTAMP',
'REVISIONTIMESTAMP:', 'REVISIONUSER', 'REVISIONUSER:', 'REVISIONYEAR',
'REVISIONYEAR:', 'ROOTPAGENAME', 'ROOTPAGENAME:', 'ROOTPAGENAMEE',
'ROOTPAGENAMEE:', 'SHORTDESC:', 'SUBJECTPAGENAME', 'SUBJECTPAGENAME:',
'SUBJECTPAGENAMEE', 'SUBJECTPAGENAMEE:', 'SUBJECTSPACE', 'SUBJECTSPACE:',
'SUBJECTSPACEE', 'SUBJECTSPACEE:', 'SUBPAGENAME', 'SUBPAGENAME:',
'SUBPAGENAMEE', 'SUBPAGENAMEE:', 'TALKPAGENAME', 'TALKPAGENAME:',
'TALKPAGENAMEE', 'TALKPAGENAMEE:', 'TALKSPACE', 'TALKSPACE:', 'TALKSPACEE',
'TALKSPACEE:', 'uc:', 'ucfirst:', 'urlencode:'
];
}
if (Array.isArray(window.smartdiffMagicWords)) {
SmartDiff.magicWords.push(...window.smartdiffMagicWords);
}
if (Array.isArray(window.smartdiffTemplates)) {
SmartDiff.templates = window.smartdiffTemplates;
}
mw.hook('wikipage.diff').add((window.sdcb = ($diff => {
window.sd = new SmartDiff($diff);
})));
mw.hook('wikipage.diff.wikitextBodyUpdate').add((window.sdcbu = ($rows => {
let $diff = $rows.closest('.diff');
let sd = SmartDiff.sds.get($diff[0]);
if (sd) {
sd.init($rows.filter(function () {
return this.checkVisibility();
}));
} else {
window.sd = new SmartDiff($diff);
}
})));
});
gargtnlaad7swna8tufndfd6hq2v7ye
769896
769895
2026-10-24T00:52:43Z
Nardog
40946
769896
javascript
text/javascript
mw.hook('wikipage.diff').remove(window.sdcb);
mw.hook('wikipage.diff.wikitextBodyUpdate').remove(window.sdcbu);
$('.smartdiff-link').contents().unwrap().end().remove();
$(':is(.diff-addedline, .diff-addedline, .diff-context) > div, .mw-diff-inline-added, .mw-diff-inline-deleted, .mw-diff-inline-changed').contents().each(function () {
if (this.previousSibling && this.previousSibling.tagName === this.tagName) {
this.previousSibling.textContent += this.textContent;
this.remove();
}
});
window.smartdiffTemplates = [
{
names: ['T', 'Tl'],
namespace: 10,
forceNs: true,
end: 1
},
{
names: ['Tlx'],
namespace: 10,
end: 1
},
{
names: ['U'],
prefix: 'Special:Contributions/',
end: 1
},
{
names: ['Re', 'Reply to', 'Ping'],
prefix: 'Special:Contributions/'
},
{
names: ['About'],
start: 3,
skipEven: true
},
{
names: ['For'],
start: 2
},
{
names: ['Other uses', 'Otheruses'],
end: 1
},
{
names: ['Section link', 'Slink'],
end: 1
},
{
names: ['Redirect'],
skipEven: true,
noRedirectEnd: 1
},
{
names: ['Shortcut'],
noRedirectStart: 1
},
{
names: ['Tracked', 'Phab'],
prefix: 'phab:',
end: 1
},
{
names: [
'Distinguish',
'Main', 'Main article',
'Further',
'See also', 'Seealso'
]
},
{
names: ['Ll'],
end: 1
}
];
mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api'
], () => {
mw.loader.addStyleTag(`body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external) {
color: #36b;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):visited {
color: #636;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):active {
color: #b63;
}`);
class SmartDiff {
constructor($diff) {
this.now = performance.now();
this.$diff = $diff;
this.isView = mw.config.get('wgNamespaceNumber') !== -1 &&
mw.config.get('wgAction') === 'view' &&
$diff.nextAll().is('.diff-hr');
[
['old', '#mw-diff-otitle1 a, #differences-prevlink'],
['new', '#mw-diff-ntitle1 a, #differences-nextlink']
].forEach(([side, selector]) => {
let link = this.$diff[0].querySelector(selector);
let pn = link && mw.util.getParamValue('title', link.search);
if (pn) {
this[side] = {
pn,
ns: mw.Title.newFromText(pn).getNamespaceId()
};
}
});
this.isSamePage = this.old?.pn === this.new?.pn;
this.titles = {};
this.noRedirLinks = new WeakSet();
SmartDiff.sds.set($diff[0], this);
this.init();
}
async init($rows = this.$diff) {
this.side = 'old';
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div);
}
this.side = 'new';
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div);
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, true);
}
let $contexts = $rows.find('.diff-context > div'), clones = [];
for (let [i, div] of $contexts.get().entries()) {
if (i % 2) {
this.side = 'new';
if (this.divHasLink) {
if (this.sidePropUsed) {
await this.processLine(div);
} else {
div.replaceChildren(
...$contexts.eq(i - 1).contents().clone()
);
clones.push(div);
}
}
} else {
this.side = 'old';
await this.processLine(div);
}
}
$(clones).find('.smartdiff-link[title]').each((_, link) => {
this.titles[link.title]?.links.push(link);
});
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, isInlineChange) {
this.divHasLink = false;
this.sidePropUsed = false;
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (isInlineChange) {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.divHasLink) return;
try {
let newDiv = this.unescape(tokens);
this.verify(div, newDiv);
div.replaceChildren(...newDiv.childNodes);
} catch (e) {
debugger;
if (typeof e === 'string') {
this.hasError = true;
console.warn(`SmartDiff error: ${e}`);
} else {
throw e;
}
}
}
tokenize(el, escSelector) {
return [...el.childNodes].flatMap(node => {
if (node.nodeType === 3) {
return node.textContent.split('').map(s => ({ s }));
}
if (node.nodeType !== 1) {
return [];
}
if (escSelector && node.matches(escSelector)) {
return [
{ openingTag: node },
{ esc: this.tokenize(node), tag: node },
{ closingTag: node }
];
}
return [
{ openingTag: node },
...this.tokenize(node, escSelector),
{ closingTag: node }
];
});
}
escapeTags(tokens, selector) {
let openTags = [], data = [];
tokens.forEach((token, i) => {
if (token.openingTag?.matches(selector)) {
openTags.push([token.openingTag, i + 1]);
} else if (token.closingTag && openTags.length &&
token.closingTag.tagName === openTags.at(-1)[0].tagName
) {
data.push([...openTags.pop(), i]);
}
});
data.reverse().forEach(([tag, start, end]) => {
let token = { tag };
token.esc = tokens.splice(start, end - start, token);
});
}
unescapeTags(tokens, selector) {
tokens.splice(0, Infinity, ...tokens.flatMap(token => (
token.esc && (!selector || token.tag.matches(selector))
? token.esc
: [token]
)));
}
unescape(tokens) {
let els = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
els.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.link) {
el = el.cloneNode();
}
els.at(-1).append(el);
els.push(el);
} else if (token.closingTag) {
let el = els.pop();
if (!els.length) {
throw `too many closing tags.\n\n${el.innerHTML}`;
}
if (el.tagName !== token.closingTag.tagName) {
throw `expected </${
token.closingTag.tagName.toLowerCase()
}>, got </${
el.tagName.toLowerCase()
}>.\n\n${els[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
els.at(-1).append(lastText);
}
if (els.length !== 1) {
throw `too many opening tags.\n\n${els[0].innerHTML}`;
}
return els[0];
}
verify(div, newDiv) {
let origHtml = div.innerHTML;
let $comp = $(newDiv).clone();
$comp.find('.smartdiff-link').contents().unwrap();
let comp = $comp.html().replace(/<\/(ins|del)><\1(?: [^>]+)?>/g, '');
if (comp !== origHtml) {
throw `changed substance.\n\nOriginal:\n${origHtml}\n\nCleaned:\n${comp}\n\nFull:\n${newDiv.innerHTML}`;
}
let emptyCells = newDiv.querySelectorAll(':is(ins, del):empty');
if (emptyCells.length) {
console.log('Empty cells found:', ...emptyCells);
mw.notify('Empty cells found');
}
}
async exec(tokens, isSecond) {
let s = tokens.map(token => token.s || '').join('');
let modes = isSecond
? ['url', 'link', 'template', 'gallery']
: ['url', 'link', 'template', 'heading', 'gallery'];
for (let mode of modes) {
await this.search(s, tokens, mode);
}
}
async search(s, tokens, mode, data) {
for (let match of s.matchAll(SmartDiff[mode + 'Re'])) {
await this[mode + 'Callback'](
tokens,
...match.indices.slice(1).map((indices, i) => indices && {
s: match[i + 1],
indices,
tokens,
tokenIndices: SmartDiff.tokenIndices
}),
data
);
}
}
static tokenIndices() {
let count = 0, start;
for (let [i, token] of this.tokens.entries()) {
if (token.s) {
if (count === this.indices[0]) {
start = i;
}
if (count === this.indices[1] - 1) {
return [start, i + 1];
}
count++;
}
}
}
static linkRe = /\[\[\s*(?:<tvar(?: [^>]*)?>\s*)?((?:[^<>\[\]{|}])+?)(?:\s*<\/tvar>|\{\{#translation:\s*\}\})?\s*(?:\||\]\])/dg;
async linkCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (s.startsWith('/')) {
t = await this.getSubpage(s);
} else if (s.startsWith('#') && (!this.isView || !this.isSamePage)) {
t = mw.Title.newFromText(this.getProp() + s);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s);
} else {
t = mw.Title.newFromText(s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
static templateRe = /((?<!{){{\s*(?:(?:#invoke|(?:safe)?subst|msg(?:nw)?|raw|int):\s*)?(?:<tvar(?: [^>]*)?>\s*)?)([^<>\[\]{|}]+?)((?:\s*<\/tvar>)?\s*(\|.*)?(?:}}|$))/dg;
async templateCallback(tokens, pre, mid, post, args) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (pre.s.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} else if (s.startsWith('#')) {
return;
} else if (pre.s.includes('int')) {
t = mw.Title.makeTitle(8, s);
} else if (s.startsWith('/')) {
t = await this.getSubpage(s, 10);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s, 10);
} else {
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
if (match && SmartDiff.magicWords.includes(match[0])) return;
}
t = mw.Title.newFromText(s, 10);
}
if (t) {
this.link(tokens, ...mid.tokenIndices(), t);
}
if (!args) return;
let [postStart, postEnd] = post.tokenIndices();
let newPost = tokens.slice(postStart, postEnd);
await this.search(post.s, newPost, 'template');
if (!t || !SmartDiff.templates) return;
let divHasLink = this.divHasLink;
this.divHasLink = false;
let name = t.getMainText();
for (let data of SmartDiff.templates) {
if (Array.isArray(data?.names) &&
Array.prototype.includes.call(data.names, name)
) {
await this.search(
post.s,
newPost,
'templateArgs',
{ ...data, i: 1 }
);
}
}
if (this.divHasLink) {
tokens.splice(postStart, postEnd - postStart, ...newPost);
} else {
this.divHasLink = divHasLink;
}
}
static templateArgsRe = /\|\s*(?:([^=|]*?)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, name, mid, data) {
let n = name ? Number(name.s) || name.s : data.i++;
if (typeof n !== 'number' || (
n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven)
)) {
return;
}
if (!mid.s) return;
let s = this.decodeWikitext(mid.s);
if (data.prefix) {
s = data.prefix + s;
}
if (data.suffix) {
s += data.suffix;
}
let t = data.forceNs
? mw.Title.makeTitle(data.namespace, s)
: mw.Title.newFromText(s, data.namespace);
if (!t) return;
this.link(
tokens,
...mid.tokenIndices(),
t,
n >= data.noRedirectStart || n <= data.noRedirectEnd
);
}
static urlRe = /(\[(?=.+\]))?((?:\bhttps?:)?\/\/[^\s"<>\[\]{|}]*)([^\s"<>\[\]{|}])/dg;
urlCallback(tokens, pre, mid, last) {
let omitLast;
if (!pre) {
if (mid.s.startsWith('//')) return;
if (last.s === ';') {
let match = mid.s.match(/&#?[\da-z]+$/i);
if (match) {
let source = match[0] + last.s;
omitLast = this.decodeWikitext(source) === source;
} else {
omitLast = true;
}
} else {
omitLast = '!,.:?'.includes(last.s) ||
last.s === ')' && mid.s.includes('(');
}
}
let s = mid.s;
if (!omitLast) {
s += last.s;
}
let [midStart, midEnd] = mid.tokenIndices();
this.link(
tokens,
midStart,
omitLast ? midEnd : last.tokenIndices()[1],
this.decodeWikitext(s)
);
}
static headingRe = /^(={1,6})\s*(.+?)\s*(\1)\s*(?=<!--|$)/dg;
headingCallback(tokens, pre, mid, post) {
if (/{{|<\/?ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s\[\]]+\]/i.test(mid.s)) return;
let s = this.decodeWikitext(mid.s).replace(
/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|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').replace(/^[\s_]+|[\s_]+$/g, '');
let t = mw.Title.newFromText(
`${!this.isView || !this.isSamePage ? this.getProp() : ''}#${s}`
);
if (!t) return;
let midIndices = mid.tokenIndices();
if (tokens.slice(...midIndices).some(({ link }) => link)) {
this.link(tokens, ...post.tokenIndices(), t);
this.link(tokens, ...pre.tokenIndices(), t);
} else {
this.link(tokens, ...midIndices, t);
}
}
// https://commons.wikimedia.org/wiki/Special:MediaStatistics
static galleryRe = /^\s*((?:[^<>\[\]{|}])+\.(?:apng|djv|djvu|flac|gif|jpe|jpeg|jpg|jps|kar|m4a|m4b|m4p|m4r|m4v|mid|midi|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpga|oga|ogg|ogm|ogv|ogx|opus|pdf|png|spx|stl|svg|tif|tiff|wav|webm|webp|xcf))\s*(?:\||$)/dgi;
galleryCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t = mw.Title.newFromText(s, 6);
if (t && t.getNamespaceId() !== 6) {
t = mw.Title.makeTitle(6, s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
link(tokens, start, end, mwtOrUrl, noRedirect) {
let indices = [], last;
tokens.slice(start, end).forEach((token, i) => {
if (!token.s && !token.link && !token.esc) return;
if (token.s && !token.link) {
if (!indices.length || last?.link || last?.esc) {
indices.push([start + i]);
}
indices.at(-1)[1] = start + i + 1;
}
last = token;
});
if (indices.some(chunkIndices => chunkIndices.length !== 2)) {
mw.notify('Corrupt chunks found');
debugger;
}
if (!indices.length) return;
let attribs = { class: 'smartdiff-link' }, obj;
if (typeof mwtOrUrl === 'string') {
attribs.class += ' external';
attribs.href = mwtOrUrl;
attribs.rel = 'nofollow';
} else {
let title = mwtOrUrl.toText();
if (title) {
if (Object.hasOwn(this.titles, title)) {
obj = this.titles[title];
if (obj.classes) {
attribs.class += ' ' + obj.classes.join(' ');
}
} else {
obj = { links: [] };
this.titles[title] = obj;
}
let fragment;
if (obj.url) {
attribs.href = obj.url;
fragment = mwtOrUrl.getFragment();
} else {
let params = obj.red && { action: 'edit', redlink: 1 } ||
noRedirect && obj.redirect && { redirect: 'no' };
if (obj.canonical) {
attribs.href = mw.util.getUrl(obj.canonical, params);
fragment = mwtOrUrl.getFragment();
} else {
attribs.href = mwtOrUrl.getUrl(params);
}
}
if (fragment) {
attribs.href += mw.util.getUrl('#' + fragment);
}
attribs.title = obj.canonical || title;
} else {
attribs.class += ' mw-selflink-fragment';
attribs.href = mwtOrUrl.getUrl();
}
}
let links = indices.reverse().map(chunkIndices => (
this.linkChunk(tokens, ...chunkIndices, attribs)
));
if (obj && !obj.processed) {
obj.links.push(...links);
if (noRedirect) {
links.forEach(link => {
this.noRedirLinks.add(link);
});
}
}
this.divHasLink = true;
}
linkChunk(tokens, start, end, attribs) {
let newStart = start, newEnd = end;
let mid = tokens.slice(start, end);
let midTags = mid.filter(token => token.openingTag || token.closingTag);
for (let i = 0; ; i += 2) {
if (tokens[newStart - 1]?.openingTag && midTags[i]?.closingTag &&
tokens[newStart - 1].openingTag.tagName === midTags[i].closingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
} else {
break;
}
}
for (let i = midTags.length - 1; ; i--) {
if (tokens[newEnd]?.closingTag && midTags[i]?.openingTag &&
tokens[newEnd].closingTag.tagName === midTags[i].openingTag.tagName
) {
midTags.push(tokens[newEnd]);
newEnd++;
} else {
break;
}
}
if (start - newStart) {
console.log(`Moved up ${start - newStart} times`);
}
if (start - newStart > 1) {
mw.notify(`Moved up ${start - newStart} times`);
}
if (newEnd - end) {
console.log(`Moved down ${newEnd - end} times`);
}
if (newEnd - end > 1) {
mw.notify(`Moved down ${newEnd - end} times`);
}
let link = $('<a>').attr(attribs)[0];
let pre = [{ openingTag: link, link }];
let post = [{ closingTag: link, link }];
if (midTags[0]?.closingTag) {
pre.unshift({ ...midTags[0] });
pre.push({ openingTag: midTags[0].closingTag });
}
if (midTags.at(-1)?.openingTag) {
post.unshift({ closingTag: midTags.at(-1).openingTag });
post.push({ ...midTags.at(-1) });
}
tokens.splice(newEnd, 0, ...post);
tokens.splice(newStart, 0, ...pre);
mid.forEach(token => {
if (token.s) {
token.link = link;
}
});
return link;
}
decodeWikitext(text, decodePercents) {
let s = text;
if (decodePercents) {
try {
s = decodeURIComponent(s);
} catch {}
}
return $($.parseHTML(s)).text();
}
async getSubpage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
return mw.Title.newFromText(
this.getProp() + s.replace(/\/+$/, '')
);
}
return mw.Title.newFromText(s, fallbackNs);
}
async getParentPage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
let chunks = s.split('/');
let levelCount = chunks.findIndex(v => v !== '..');
let sup = this.getProp().split('/').slice(0, -levelCount).join('/');
if (sup) {
let sub = chunks.slice(levelCount).join('/').replace(/\/+$/, '');
return mw.Title.newFromText(sub ? `${sup}/${sub}` : sup);
}
}
return mw.Title.newFromText(s, fallbackNs);
}
async canHaveSubpages(ns = this.getProp('ns')) {
SmartDiff.subNs = SmartDiff.subNs ||
mw.config.get('wgVisualEditorConfig')?.namespacesWithSubpages ||
Object.values((await new mw.Api().get({
action: 'query',
meta: 'siteinfo',
siprop: 'namespaces',
formatversion: 2
})).query.namespaces).filter(n => n.subpages).map(n => n.id);
return SmartDiff.subNs.includes(ns);
}
getProp(n = 'pn') {
if (this[this.side]) {
this.sidePropUsed = !this.isSamePage;
return this[this.side][n];
}
if (Object.hasOwn(this, n)) {
return this[n];
}
this.pn = mw.config.get('wgPageName');
this.ns = mw.config.get('wgNamespaceNumber');
return this[n];
}
async queryTitles() {
let unprocessed = Object.entries(this.titles)
.filter(([, v]) => !v.processed).slice(0, 500).map(([k]) => k);
while (unprocessed.length) {
let curTitles = unprocessed.slice(0, 50);
curTitles.forEach(title => {
this.titles[title].processed = true;
});
let { query } = await new mw.Api().post({
action: 'query',
titles: curTitles,
iwurl: 1,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: this.getProp(),
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.pages?.forEach(p => {
let obj = (this.titles[p.title] ??= {});
let classes = p.linkclasses || [];
if (p.missing && !p.known) {
classes.push('new');
obj.red = true;
} else if (p.redirect) {
obj.redirect = true;
}
if (classes.length) {
obj.classes = classes;
}
});
query.interwiki?.forEach(iw => {
this.titles[iw.title] = { classes: ['extiw'], url: iw.url };
});
query.normalized?.forEach(({ from, to }) => {
this.titles[from] = {
...this.titles[from],
...this.titles[to],
canonical: to
};
});
curTitles.forEach(title => {
let obj = this.titles[title];
if (obj.classes) {
$(obj.links).addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$(obj.links).attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
noRedirLinks.has(this) && { redirect: 'no' }
) + this.hash;
});
} else {
let url = obj.url ||
obj.red && mw.util.getUrl(
obj.canonical || title,
{ action: 'edit', redlink: 1 }
) ||
obj.canonical && mw.util.getUrl(obj.canonical);
if (url) {
$(obj.links).attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$(obj.links).attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
static sds = new WeakMap();
static magicWords = [
'!', '=', 'BASEPAGENAME', 'BASEPAGENAME:', 'BASEPAGENAMEE', 'BASEPAGENAMEE:',
'canonicalurl:', 'CURRENTDAY', 'CURRENTDAY2', 'CURRENTDAYNAME', 'CURRENTDOW',
'CURRENTHOUR', 'CURRENTMONTH', 'CURRENTMONTH1', 'CURRENTMONTHABBREV',
'CURRENTMONTHNAME', 'CURRENTMONTHNAMEGEN', 'CURRENTTIME', 'CURRENTTIMESTAMP',
'CURRENTVERSION', 'CURRENTWEEK', 'CURRENTYEAR', 'DEFAULTCATEGORYSORT:',
'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DISPLAYTITLE:', 'filepath:', 'formatnum:',
'FULLPAGENAME', 'FULLPAGENAME:', 'FULLPAGENAMEE', 'FULLPAGENAMEE:', 'fullurl:',
'gender:', 'lc:', 'lcfirst:', 'LOCALDAY', 'LOCALDAY2', 'LOCALDAYNAME',
'LOCALDOW', 'LOCALHOUR', 'LOCALMONTH', 'LOCALMONTH1', 'LOCALMONTHABBREV',
'LOCALMONTHNAME', 'LOCALMONTHNAMEGEN', 'LOCALTIME', 'LOCALTIMESTAMP',
'LOCALWEEK', 'LOCALYEAR','NAMESPACE', 'NAMESPACE:', 'NAMESPACEE', 'NAMESPACEE:',
'NAMESPACENUMBER', 'NAMESPACENUMBER:', 'ns:', 'NUMBEROFACTIVEUSERS',
'NUMBEROFARTICLES', 'NUMBEROFEDITS', 'NUMBEROFFILES', 'NUMBEROFPAGES',
'NUMBEROFUSERS', 'padleft:', 'PAGENAME', 'PAGENAMEE', 'PAGESINCAT:',
'PAGESINCATEGORY:', 'plural:', 'REVISIONDAY', 'REVISIONDAY:', 'REVISIONDAY2',
'REVISIONDAY2:', 'REVISIONID', 'REVISIONID:', 'REVISIONMONTH', 'REVISIONMONTH:',
'REVISIONMONTH1', 'REVISIONMONTH1:', 'REVISIONSIZE', 'REVISIONTIMESTAMP',
'REVISIONTIMESTAMP:', 'REVISIONUSER', 'REVISIONUSER:', 'REVISIONYEAR',
'REVISIONYEAR:', 'ROOTPAGENAME', 'ROOTPAGENAME:', 'ROOTPAGENAMEE',
'ROOTPAGENAMEE:', 'SHORTDESC:', 'SUBJECTPAGENAME', 'SUBJECTPAGENAME:',
'SUBJECTPAGENAMEE', 'SUBJECTPAGENAMEE:', 'SUBJECTSPACE', 'SUBJECTSPACE:',
'SUBJECTSPACEE', 'SUBJECTSPACEE:', 'SUBPAGENAME', 'SUBPAGENAME:',
'SUBPAGENAMEE', 'SUBPAGENAMEE:', 'TALKPAGENAME', 'TALKPAGENAME:',
'TALKPAGENAMEE', 'TALKPAGENAMEE:', 'TALKSPACE', 'TALKSPACE:', 'TALKSPACEE',
'TALKSPACEE:', 'uc:', 'ucfirst:', 'urlencode:'
];
}
if (Array.isArray(window.smartdiffMagicWords)) {
SmartDiff.magicWords.push(...window.smartdiffMagicWords);
}
if (Array.isArray(window.smartdiffTemplates)) {
SmartDiff.templates = window.smartdiffTemplates;
}
mw.hook('wikipage.diff').add((window.sdcb = ($diff => {
window.sd = new SmartDiff($diff);
})));
mw.hook('wikipage.diff.wikitextBodyUpdate').add((window.sdcbu = ($rows => {
let $diff = $rows.closest('.diff');
let sd = SmartDiff.sds.get($diff[0]);
if (sd) {
sd.init($rows.filter(function () {
return this.checkVisibility();
}));
} else {
window.sd = new SmartDiff($diff);
}
})));
});
t4pbk721w4kd1xtkel4j6rj69k2hoce
769907
769896
2026-10-24T03:39:55Z
Nardog
40946
769907
javascript
text/javascript
mw.hook('wikipage.diff').remove(window.sdcb);
mw.hook('wikipage.diff.wikitextBodyUpdate').remove(window.sdcbu);
$('.smartdiff-link').contents().unwrap().end().remove();
$(':is(.diff-addedline, .diff-addedline, .diff-context) > div, .mw-diff-inline-added, .mw-diff-inline-deleted, .mw-diff-inline-changed').contents().each(function () {
if (this.previousSibling && this.previousSibling.tagName === this.tagName) {
this.previousSibling.textContent += this.textContent;
this.remove();
}
});
window.smartdiffTemplates = [
{
names: ['T', 'Tl'],
namespace: 10,
forceNs: true,
end: 1
},
{
names: ['Tlx'],
namespace: 10,
end: 1
},
{
names: ['U'],
prefix: 'Special:Contributions/',
end: 1
},
{
names: ['Re', 'Reply to', 'Ping'],
prefix: 'Special:Contributions/'
},
{
names: ['About'],
start: 3,
skipEven: true
},
{
names: ['For'],
start: 2
},
{
names: ['Other uses', 'Otheruses'],
end: 1
},
{
names: ['Section link', 'Slink'],
end: 1
},
{
names: ['Redirect'],
skipEven: true,
noRedirectEnd: 1
},
{
names: ['Shortcut'],
noRedirectStart: 1
},
{
names: ['Tracked', 'Phab'],
prefix: 'phab:',
end: 1
},
{
names: [
'Distinguish',
'Main', 'Main article',
'Further',
'See also', 'Seealso'
]
},
{
names: ['Ll'],
end: 1
}
];
mw.loader.using([
'mediawiki.util', 'mediawiki.Title', 'mediawiki.api'
], () => {
mw.loader.addStyleTag(`body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external) {
color: #36b;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):visited {
color: #636;
}
body:is(.skin-vector-legacy, .skin-monobook) .smartdiff-link:is(.extiw, .external):active {
color: #b63;
}`);
class SmartDiff {
constructor($diff) {
this.now = performance.now();
this.$diff = $diff;
this.isView = mw.config.get('wgNamespaceNumber') !== -1 &&
mw.config.get('wgAction') === 'view' &&
$diff.nextAll().is('.diff-hr');
[
['old', '#mw-diff-otitle1 a, #differences-prevlink'],
['new', '#mw-diff-ntitle1 a, #differences-nextlink']
].forEach(([side, selector]) => {
let link = this.$diff[0].querySelector(selector);
let pn = link && mw.util.getParamValue('title', link.search);
if (pn) {
this[side] = {
pn,
ns: mw.Title.newFromText(pn).getNamespaceId()
};
}
});
this.isSamePage = this.old?.pn === this.new?.pn;
this.titles = {};
this.noRedirLinks = new WeakSet();
SmartDiff.sds.set($diff[0], this);
this.init();
}
async init($rows = this.$diff) {
this.side = 'old';
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div);
}
this.side = 'new';
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div);
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, true);
}
let $contexts = $rows.find('.diff-context > div'), clones = [];
for (let [i, div] of $contexts.get().entries()) {
if (i % 2) {
this.side = 'new';
if (this.divHasLink) {
if (this.sidePropUsed) {
await this.processLine(div);
} else {
div.replaceChildren(
...$contexts.eq(i - 1).contents().clone()
);
clones.push(div);
}
}
} else {
this.side = 'old';
await this.processLine(div);
}
}
$(clones).find('.smartdiff-link[title]').each((_, link) => {
this.titles[link.title]?.links.push(link);
});
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, isInlineChange) {
this.divHasLink = false;
this.sidePropUsed = false;
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (isInlineChange) {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.divHasLink) return;
try {
let newDiv = this.unescape(tokens);
this.verify(div, newDiv);
div.replaceChildren(...newDiv.childNodes);
} catch (e) {
debugger;
if (typeof e === 'string') {
this.hasError = true;
console.warn(`SmartDiff error: ${e}`);
} else {
throw e;
}
}
}
tokenize(el, escSelector) {
return [...el.childNodes].flatMap(node => {
if (node.nodeType === 3) {
return node.textContent.split('').map(s => ({ s }));
}
if (node.nodeType !== 1) {
return [];
}
if (escSelector && node.matches(escSelector)) {
return [
{ openingTag: node },
{ esc: this.tokenize(node), tag: node },
{ closingTag: node }
];
}
return [
{ openingTag: node },
...this.tokenize(node, escSelector),
{ closingTag: node }
];
});
}
escapeTags(tokens, selector) {
let openTags = [], data = [];
tokens.forEach((token, i) => {
if (token.openingTag?.matches(selector)) {
openTags.push([token.openingTag, i + 1]);
} else if (token.closingTag && openTags.length &&
token.closingTag.tagName === openTags.at(-1)[0].tagName
) {
data.push([...openTags.pop(), i]);
}
});
data.reverse().forEach(([tag, start, end]) => {
let token = { tag };
token.esc = tokens.splice(start, end - start, token);
});
}
unescapeTags(tokens, selector) {
tokens.splice(0, Infinity, ...tokens.flatMap(token => (
token.esc && (!selector || token.tag.matches(selector))
? token.esc
: [token]
)));
}
unescape(tokens) {
let els = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
els.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.link) {
el = el.cloneNode();
}
els.at(-1).append(el);
els.push(el);
} else if (token.closingTag) {
let el = els.pop();
if (!els.length) {
throw `too many closing tags.\n\n${el.innerHTML}`;
}
if (el.tagName !== token.closingTag.tagName) {
throw `expected </${
token.closingTag.tagName.toLowerCase()
}>, got </${
el.tagName.toLowerCase()
}>.\n\n${els[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
els.at(-1).append(lastText);
}
if (els.length !== 1) {
throw `too many opening tags.\n\n${els[0].innerHTML}`;
}
return els[0];
}
verify(div, newDiv) {
let origHtml = div.innerHTML;
let $comp = $(newDiv).clone();
$comp.find('.smartdiff-link').contents().unwrap();
let comp = $comp.html().replace(/<\/(ins|del)><\1(?: [^>]+)?>/g, '');
if (comp !== origHtml) {
throw `changed substance.\n\nOriginal:\n${origHtml}\n\nCleaned:\n${comp}\n\nFull:\n${newDiv.innerHTML}`;
}
let emptyCells = newDiv.querySelectorAll(':is(ins, del):empty');
if (emptyCells.length) {
console.log('Empty cells found:', ...emptyCells);
mw.notify('Empty cells found');
}
}
async exec(tokens, isSecond) {
let s = tokens.map(token => token.s || '').join('');
let modes = isSecond
? ['url', 'link', 'template', 'gallery']
: ['url', 'link', 'template', 'heading', 'gallery'];
for (let mode of modes) {
await this.search(s, tokens, mode);
}
}
async search(s, tokens, mode, data) {
for (let match of s.matchAll(SmartDiff[mode + 'Re'])) {
await this[mode + 'Callback'](
tokens,
...match.indices.slice(1).map((indices, i) => indices && {
s: match[i + 1],
indices,
tokens,
tokenIndices: SmartDiff.tokenIndices
}),
data
);
}
}
static tokenIndices() {
let count = 0, start;
for (let [i, token] of this.tokens.entries()) {
if (token.s) {
if (count === this.indices[0]) {
start = i;
}
if (count === this.indices[1] - 1) {
return [start, i + 1];
}
count++;
}
}
}
static linkRe = /\[\[\s*(?:<tvar(?: [^>]*)?>\s*)?((?:[^<>\[\]{|}])+?)(?:\s*<\/tvar>|\{\{#translation:\s*\}\})?\s*(?:\||\]\])/dg;
async linkCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (s.startsWith('/')) {
t = await this.getSubpage(s);
} else if (s.startsWith('#') && (!this.isView || !this.isSamePage)) {
t = mw.Title.newFromText(this.getProp() + s);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s);
} else {
t = mw.Title.newFromText(s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
static templateRe = /((?<!{){{\s*(?:(?:#invoke|(?:safe)?subst|msg(?:nw)?|raw|int):\s*)?(?:<tvar(?: [^>]*)?>\s*)?)([^<>\[\]{|}]+?)((?:\s*<\/tvar>)?\s*(\|.*)?(?:}}|$))/dg;
async templateCallback(tokens, pre, mid, post, args) {
let s = this.decodeWikitext(mid.s, true);
let t;
if (pre.s.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} else if (s.startsWith('#')) {
return;
} else if (pre.s.includes('int')) {
t = mw.Title.makeTitle(8, s);
} else if (s.startsWith('/')) {
t = await this.getSubpage(s, 10);
} else if (s.startsWith('../')) {
t = await this.getParentPage(s, 10);
} else {
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
if (match && SmartDiff.magicWords.includes(match[0])) return;
}
t = mw.Title.newFromText(s, 10);
}
if (t) {
this.link(tokens, ...mid.tokenIndices(), t);
}
if (!args) return;
let [postStart, postEnd] = post.tokenIndices();
let newPost = tokens.slice(postStart, postEnd);
await this.search(post.s, newPost, 'template');
if (!t || !SmartDiff.templates) return;
let divHasLink = this.divHasLink;
this.divHasLink = false;
let name = t.getMainText();
for (let data of SmartDiff.templates) {
if (Array.isArray(data?.names) &&
Array.prototype.includes.call(data.names, name)
) {
await this.search(
post.s,
newPost,
'templateArgs',
{ ...data, i: 1 }
);
}
}
if (this.divHasLink) {
tokens.splice(postStart, postEnd - postStart, ...newPost);
} else {
this.divHasLink = divHasLink;
}
}
static templateArgsRe = /\|\s*(?:([^=|]*?)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, name, mid, data) {
let n = name ? Number(name.s) || name.s : data.i++;
if (typeof n !== 'number' || (
n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven)
)) {
return;
}
if (!mid.s) return;
let s = this.decodeWikitext(mid.s);
if (data.prefix) {
s = data.prefix + s;
}
if (data.suffix) {
s += data.suffix;
}
let t = data.forceNs
? mw.Title.makeTitle(data.namespace, s)
: mw.Title.newFromText(s, data.namespace);
if (!t) return;
this.link(
tokens,
...mid.tokenIndices(),
t,
n >= data.noRedirectStart || n <= data.noRedirectEnd
);
}
static urlRe = /(\[(?=.+\]))?((?:\bhttps?:)?\/\/[^\s"<>\[\]{|}]*)([^\s"<>\[\]{|}])/dg;
urlCallback(tokens, pre, mid, last) {
let omitLast;
if (!pre) {
if (mid.s.startsWith('//')) return;
if (last.s === ';') {
let match = mid.s.match(/&#?[\da-z]+$/i);
if (match) {
let source = match[0] + last.s;
omitLast = this.decodeWikitext(source) === source;
} else {
omitLast = true;
}
} else {
omitLast = '!,.:?'.includes(last.s) ||
last.s === ')' && mid.s.includes('(');
}
}
let s = mid.s;
if (!omitLast) {
s += last.s;
}
let [midStart, midEnd] = mid.tokenIndices();
this.link(
tokens,
midStart,
omitLast ? midEnd : last.tokenIndices()[1],
this.decodeWikitext(s)
);
}
static headingRe = /^(={1,6})\s*(.+?)\s*(\1)\s*(?=<!--|$)/dg;
headingCallback(tokens, pre, mid, post) {
if (/{{|<\/?ref(?:\s[^>]*)?>|\[(?:https?:)?\/\/[^\s\[\]]+\]/i.test(mid.s)) return;
let s = this.decodeWikitext(mid.s).replace(
/'''(.+?)'''|\[\[:?(?:[^|\]]+\|)?([^\]]+)\]\]|<\/?(?:abbr|b|bdi|bdo|big|cite|code|data|del|dfn|em|font|i|ins|kbd|mark|nowiki|q|rb|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').replace(/^[\s_]+|[\s_]+$/g, '');
let t = mw.Title.newFromText(
`${!this.isView || !this.isSamePage ? this.getProp() : ''}#${s}`
);
if (!t) return;
let midIndices = mid.tokenIndices();
if (tokens.slice(...midIndices).some(({ link }) => link)) {
this.link(tokens, ...post.tokenIndices(), t);
this.link(tokens, ...pre.tokenIndices(), t);
} else {
this.link(tokens, ...midIndices, t);
}
}
// https://commons.wikimedia.org/wiki/Special:MediaStatistics
static galleryRe = /^\s*((?:[^<>\[\]{|}])+\.(?:apng|djv|djvu|flac|gif|jpe|jpeg|jpg|jps|kar|m4a|m4b|m4p|m4r|m4v|mid|midi|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpga|oga|ogg|ogm|ogv|ogx|opus|pdf|png|spx|stl|svg|tif|tiff|wav|webm|webp|xcf))\s*(?:\||$)/dgi;
galleryCallback(tokens, mid) {
let s = this.decodeWikitext(mid.s, true);
let t = mw.Title.newFromText(s, 6);
if (t && t.getNamespaceId() !== 6) {
t = mw.Title.makeTitle(6, s);
}
if (!t) return;
this.link(tokens, ...mid.tokenIndices(), t);
}
link(tokens, start, end, mwtOrUrl, noRedirect) {
let indices = [], last;
tokens.slice(start, end).forEach((token, i) => {
if (!token.s && !token.link && !token.esc) return;
if (token.s && !token.link) {
if (!indices.length || last?.link || last?.esc) {
indices.push([start + i]);
}
indices.at(-1)[1] = start + i + 1;
}
last = token;
});
if (indices.some(chunkIndices => chunkIndices.length !== 2)) {
mw.notify('Corrupt chunks found');
debugger;
}
if (!indices.length) return;
let attribs = { class: 'smartdiff-link' }, obj;
if (typeof mwtOrUrl === 'string') {
attribs.class += ' external';
attribs.href = mwtOrUrl;
attribs.rel = 'nofollow';
} else {
let title = mwtOrUrl.toText();
if (title) {
if (Object.hasOwn(this.titles, title)) {
obj = this.titles[title];
if (obj.classes) {
attribs.class += ' ' + obj.classes.join(' ');
}
} else {
obj = { links: [] };
this.titles[title] = obj;
}
let fragment;
if (obj.url) {
attribs.href = obj.url;
fragment = mwtOrUrl.getFragment();
} else {
let params = obj.red && { action: 'edit', redlink: 1 } ||
noRedirect && obj.redirect && { redirect: 'no' };
if (obj.canonical) {
attribs.href = mw.util.getUrl(obj.canonical, params);
fragment = mwtOrUrl.getFragment();
} else {
attribs.href = mwtOrUrl.getUrl(params);
}
}
if (fragment) {
attribs.href += mw.util.getUrl('#' + fragment);
}
attribs.title = obj.canonical || title;
} else {
attribs.class += ' mw-selflink-fragment';
attribs.href = mwtOrUrl.getUrl();
}
}
let links = indices.reverse().map(chunkIndices => (
this.linkChunk(tokens, ...chunkIndices, attribs)
));
if (obj && !obj.processed) {
obj.links.push(...links);
if (noRedirect) {
links.forEach(link => {
this.noRedirLinks.add(link);
});
}
}
this.divHasLink = true;
}
linkChunk(tokens, start, end, attribs) {
let mid = tokens.slice(start, end);
let midTags = mid.filter(token => token.openingTag || token.closingTag);
let newStart = start, i = 0;
while (tokens[newStart - 1]?.openingTag && midTags[i]?.closingTag &&
tokens[newStart - 1].openingTag.tagName === midTags[i].closingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
i += 2;
}
let newEnd = end, j = midTags.length - 1;
while (tokens[newEnd]?.closingTag && midTags[j]?.openingTag &&
tokens[newEnd].closingTag.tagName === midTags[j].openingTag.tagName
) {
midTags.push(tokens[newEnd]);
newEnd++;
j--;
}
if (start - newStart) {
console.log(`Moved up ${start - newStart} times`);
}
if (start - newStart > 1) {
mw.notify(`Moved up ${start - newStart} times`);
}
if (newEnd - end) {
console.log(`Moved down ${newEnd - end} times`);
}
if (newEnd - end > 1) {
mw.notify(`Moved down ${newEnd - end} times`);
}
let link = $('<a>').attr(attribs)[0];
let pre = [{ openingTag: link, link }];
let post = [{ closingTag: link, link }];
if (midTags[0]?.closingTag) {
pre.unshift({ ...midTags[0] });
pre.push({ openingTag: midTags[0].closingTag });
}
if (midTags.at(-1)?.openingTag) {
post.unshift({ closingTag: midTags.at(-1).openingTag });
post.push({ ...midTags.at(-1) });
}
tokens.splice(newEnd, 0, ...post);
tokens.splice(newStart, 0, ...pre);
mid.forEach(token => {
if (token.s) {
token.link = link;
}
});
return link;
}
decodeWikitext(text, decodePercents) {
let s = text;
if (decodePercents) {
try {
s = decodeURIComponent(s);
} catch {}
}
return $($.parseHTML(s)).text();
}
async getSubpage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
return mw.Title.newFromText(
this.getProp() + s.replace(/\/+$/, '')
);
}
return mw.Title.newFromText(s, fallbackNs);
}
async getParentPage(s, fallbackNs) {
if (await this.canHaveSubpages()) {
let chunks = s.split('/');
let levelCount = chunks.findIndex(v => v !== '..');
let sup = this.getProp().split('/').slice(0, -levelCount).join('/');
if (sup) {
let sub = chunks.slice(levelCount).join('/').replace(/\/+$/, '');
return mw.Title.newFromText(sub ? `${sup}/${sub}` : sup);
}
}
return mw.Title.newFromText(s, fallbackNs);
}
async canHaveSubpages(ns = this.getProp('ns')) {
SmartDiff.subNs = SmartDiff.subNs ||
mw.config.get('wgVisualEditorConfig')?.namespacesWithSubpages ||
Object.values((await new mw.Api().get({
action: 'query',
meta: 'siteinfo',
siprop: 'namespaces',
formatversion: 2
})).query.namespaces).filter(n => n.subpages).map(n => n.id);
return SmartDiff.subNs.includes(ns);
}
getProp(n = 'pn') {
if (this[this.side]) {
this.sidePropUsed = !this.isSamePage;
return this[this.side][n];
}
if (Object.hasOwn(this, n)) {
return this[n];
}
this.pn = mw.config.get('wgPageName');
this.ns = mw.config.get('wgNamespaceNumber');
return this[n];
}
async queryTitles() {
let unprocessed = Object.entries(this.titles)
.filter(([, v]) => !v.processed).slice(0, 500).map(([k]) => k);
while (unprocessed.length) {
let curTitles = unprocessed.slice(0, 50);
curTitles.forEach(title => {
this.titles[title].processed = true;
});
let { query } = await new mw.Api().post({
action: 'query',
titles: curTitles,
iwurl: 1,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: this.getProp(),
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.pages?.forEach(p => {
let obj = (this.titles[p.title] ??= {});
let classes = p.linkclasses || [];
if (p.missing && !p.known) {
classes.push('new');
obj.red = true;
} else if (p.redirect) {
obj.redirect = true;
}
if (classes.length) {
obj.classes = classes;
}
});
query.interwiki?.forEach(iw => {
this.titles[iw.title] = { classes: ['extiw'], url: iw.url };
});
query.normalized?.forEach(({ from, to }) => {
this.titles[from] = {
...this.titles[from],
...this.titles[to],
canonical: to
};
});
curTitles.forEach(title => {
let obj = this.titles[title];
if (obj.classes) {
$(obj.links).addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$(obj.links).attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
noRedirLinks.has(this) && { redirect: 'no' }
) + this.hash;
});
} else {
let url = obj.url ||
obj.red && mw.util.getUrl(
obj.canonical || title,
{ action: 'edit', redlink: 1 }
) ||
obj.canonical && mw.util.getUrl(obj.canonical);
if (url) {
$(obj.links).attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$(obj.links).attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
static sds = new WeakMap();
static magicWords = [
'!', '=', 'BASEPAGENAME', 'BASEPAGENAME:', 'BASEPAGENAMEE', 'BASEPAGENAMEE:',
'canonicalurl:', 'CURRENTDAY', 'CURRENTDAY2', 'CURRENTDAYNAME', 'CURRENTDOW',
'CURRENTHOUR', 'CURRENTMONTH', 'CURRENTMONTH1', 'CURRENTMONTHABBREV',
'CURRENTMONTHNAME', 'CURRENTMONTHNAMEGEN', 'CURRENTTIME', 'CURRENTTIMESTAMP',
'CURRENTVERSION', 'CURRENTWEEK', 'CURRENTYEAR', 'DEFAULTCATEGORYSORT:',
'DEFAULTSORT:', 'DEFAULTSORTKEY:', 'DISPLAYTITLE:', 'filepath:', 'formatnum:',
'FULLPAGENAME', 'FULLPAGENAME:', 'FULLPAGENAMEE', 'FULLPAGENAMEE:', 'fullurl:',
'gender:', 'lc:', 'lcfirst:', 'LOCALDAY', 'LOCALDAY2', 'LOCALDAYNAME',
'LOCALDOW', 'LOCALHOUR', 'LOCALMONTH', 'LOCALMONTH1', 'LOCALMONTHABBREV',
'LOCALMONTHNAME', 'LOCALMONTHNAMEGEN', 'LOCALTIME', 'LOCALTIMESTAMP',
'LOCALWEEK', 'LOCALYEAR','NAMESPACE', 'NAMESPACE:', 'NAMESPACEE', 'NAMESPACEE:',
'NAMESPACENUMBER', 'NAMESPACENUMBER:', 'ns:', 'NUMBEROFACTIVEUSERS',
'NUMBEROFARTICLES', 'NUMBEROFEDITS', 'NUMBEROFFILES', 'NUMBEROFPAGES',
'NUMBEROFUSERS', 'padleft:', 'PAGENAME', 'PAGENAMEE', 'PAGESINCAT:',
'PAGESINCATEGORY:', 'plural:', 'REVISIONDAY', 'REVISIONDAY:', 'REVISIONDAY2',
'REVISIONDAY2:', 'REVISIONID', 'REVISIONID:', 'REVISIONMONTH', 'REVISIONMONTH:',
'REVISIONMONTH1', 'REVISIONMONTH1:', 'REVISIONSIZE', 'REVISIONTIMESTAMP',
'REVISIONTIMESTAMP:', 'REVISIONUSER', 'REVISIONUSER:', 'REVISIONYEAR',
'REVISIONYEAR:', 'ROOTPAGENAME', 'ROOTPAGENAME:', 'ROOTPAGENAMEE',
'ROOTPAGENAMEE:', 'SHORTDESC:', 'SUBJECTPAGENAME', 'SUBJECTPAGENAME:',
'SUBJECTPAGENAMEE', 'SUBJECTPAGENAMEE:', 'SUBJECTSPACE', 'SUBJECTSPACE:',
'SUBJECTSPACEE', 'SUBJECTSPACEE:', 'SUBPAGENAME', 'SUBPAGENAME:',
'SUBPAGENAMEE', 'SUBPAGENAMEE:', 'TALKPAGENAME', 'TALKPAGENAME:',
'TALKPAGENAMEE', 'TALKPAGENAMEE:', 'TALKSPACE', 'TALKSPACE:', 'TALKSPACEE',
'TALKSPACEE:', 'uc:', 'ucfirst:', 'urlencode:'
];
}
if (Array.isArray(window.smartdiffMagicWords)) {
SmartDiff.magicWords.push(...window.smartdiffMagicWords);
}
if (Array.isArray(window.smartdiffTemplates)) {
SmartDiff.templates = window.smartdiffTemplates;
}
mw.hook('wikipage.diff').add((window.sdcb = ($diff => {
window.sd = new SmartDiff($diff);
})));
mw.hook('wikipage.diff.wikitextBodyUpdate').add((window.sdcbu = ($rows => {
let $diff = $rows.closest('.diff');
let sd = SmartDiff.sds.get($diff[0]);
if (sd) {
sd.init($rows.filter(function () {
return this.checkVisibility();
}));
} else {
window.sd = new SmartDiff($diff);
}
})));
});
l713yzleiwxnf8kji5cfzwvwx2srvn6
Template:Sandbox/SandboxSandbox
10
179269
769908
768512
2026-10-24T07:58:13Z
臺灣象象
71227
臺灣象象 moved page [[Template:Sandbox/Sandbox.css]] to [[Template:Sandbox/Sandbox]] without leaving a redirect: 並以[[Special:PermaLink/768512|修訂版本768512]]的內容建立模板樣式重新導向
768512
sanitized-css
text/css
/*#REDIRECT [[Template:Sandbox/Test.css]]
{{Redirect category shell|
{{R from move}}
}}*/
*{display:none}
s8tdue4phqdffo4l7agc8zisp45mh73
769910
769908
2026-10-24T07:58:46Z
臺灣象象
71227
臺灣象象 moved page [[Template:Sandbox/Sandbox]] to [[Template:Sandbox/SandboxSandbox]] without leaving a redirect: 並以[[Special:PermaLink/769908|修訂版本769908]]的內容建立模板樣式重新導向:Test:Testq
768512
sanitized-css
text/css
/*#REDIRECT [[Template:Sandbox/Test.css]]
{{Redirect category shell|
{{R from move}}
}}*/
*{display:none}
s8tdue4phqdffo4l7agc8zisp45mh73
Traces of Your Lipstick
0
179451
769900
769805
2026-10-24T02:29:18Z
Jaheim King
76391
769900
wikitext
text/x-wiki
{{Use mdy dates|date=May 2025}}
{{Infobox album
| name = Traces of Your Lipstick
| type = studio
| artist = [[Xscape (group)|Xscape]]
| cover =
| released = May 12, 2008
| recorded = 2007–2008
| genre = [[Contemporary R&B|R&B]]
| length =
| label = {{hlist|[[Roc-A-Wear Records|Roc-A-Wear]]|[[Interscope Records|Interscope]]}}
| producer =
*[[Rodney Jerkins]]
*[[Jaheim Hines]]
*[[Raphael Saadiq]]
*[[Fred Jerkins III]]
*[[Babyface (musician)|Babyface]]
*[[Tamika Scott]]
*[[Dallas Austin]]
*Soulpower Productions
*[[Timbaland]]
| prev_title = [[On the Hook (album)|On the Hook]]
| prev_year = 2005
| next_title = [[Here for It]]
| next_year = 2018
| misc = {{Singles
| name = Traces of Your Lipstick
| type = studio
| single1 = [[Believe Again (Xscape song)|Believe Again]]
| single1date = February 14, 2008
| single2 = [[Good Luck Charm (Xscape song)|Good Luck Charm]]
| single2date = July 7, 2008
| single3 = The Color of Love
| single3date = September 29, 2008
}}
}}
'''''Traces of Your Lipstick''''' is the sixth studio album by American R&B group [[Xscape (group)|Xscape]]. The album was released on May 12, 2008, through [[Roc-A-Wear Records]] and [[Interscope Records]].
==Background and release==
==Promotion==
==Track listing==
==Credits and personnel==
==Charts==
==Certifications==
==References==
{{Reflist}}
{{Xscape}}
{{Authority control}}
{{DEFAULTSORT:Traces Of Your Lipstick}}
[[Category:2008 albums]]
[[Category:Albums produced by Jaheim Hines]]
[[Category:Albums produced by Rodney Jerkins]]
[[Category:Xscape (group) albums]]
1le65iquumfzmttqogrs9fawq45abge
Show Me Your Soul (EP)
0
179454
769899
2026-10-24T02:14:36Z
~2026-56798-23
76390
Created page with "{{Use mdy dates|date=June 2025}} {{Infobox album | name = Show Me Your Soul | type = ep | artist = [[Red Hot and Foo]] | cover = | alt = | released = November 30, 1997 | recorded = 1997 | venue = | studio = | genre = {{hlist|[[Alternative rock]]|[[Christmas music|Christmas]]}} | length = | label = [[Sony Records|Sony]] | producer = [[Lenny Kravitz]] | prev_title = [[On Your Face (album)|On Your Face]] | prev_year..."
769899
wikitext
text/x-wiki
{{Use mdy dates|date=June 2025}}
{{Infobox album
| name = Show Me Your Soul
| type = ep
| artist = [[Red Hot and Foo]]
| cover =
| alt =
| released = November 30, 1997
| recorded = 1997
| venue =
| studio =
| genre = {{hlist|[[Alternative rock]]|[[Christmas music|Christmas]]}}
| length =
| label = [[Sony Records|Sony]]
| producer = [[Lenny Kravitz]]
| prev_title = [[On Your Face (album)|On Your Face]]
| prev_year = 1996
| next_title = [[Truth in Soul]]
| next_year = 1998
}}
'''''Show Me Your Soul''''' is a four-song Christmas [[extended play]] (EP) release by the alternative rock band [[Red Hot and Foo]] in November 30, 1997.
== Track listing ==
==Personnel==
==Charts==
==References==
{{Reflist}}
{{Red Hot and Foo}}
{{Authority control}}
[[Category:Red Hot and Foo EPs]]
[[Category:1997 EPs]]
[[Category:Albums produced by Lenny Kravitz]]
[[Category:Christmas EPs]]
et2wny67sbvpg6xr7pkg1zl7mocvjw9
Template:Sandbox/Sandbox.css
10
179455
769909
2026-10-24T07:58:16Z
臺灣象象
71227
臺灣象象移動頁面[[Template:Sandbox/Sandbox.css]]至[[Template:Sandbox/Sandbox]]並以[[Special:PermaLink/768512|修訂版本768512]]的內容建立模板樣式重新導向
769909
sanitized-css
text/css
/*#REDIRECT [[Template:Sandbox/Sandbox]]
{{Redirect category shell|
{{R from move}}
}}
[[Category:模板樣式重新導向]]*/
/*#REDIRECT [[Template:Sandbox/Test.css]]
{{Redirect category shell|
{{R from move}}
}}*/
*{display:none}
i8dcmiersa5itra1fgalo4uuth6350t
Template:Sandbox/Sandbox
10
179456
769911
2026-10-24T07:58:49Z
臺灣象象
71227
臺灣象象移動頁面[[Template:Sandbox/Sandbox]]至[[Template:Sandbox/SandboxSandbox]]並以[[Special:PermaLink/769908|修訂版本769908]]的內容建立模板樣式重新導向:Test:Testq
769911
sanitized-css
text/css
/*#REDIRECT [[Template:Sandbox/SandboxSandbox]]
{{Redirect category shell|
{{R from move}}
}}
[[Category:模板樣式重新導向]]*/
/*#REDIRECT [[Template:Sandbox/Test.css]]
{{Redirect category shell|
{{R from move}}
}}*/
*{display:none}
dkuj80hwbqbhwkjszn0le6kfwbw9q34
User:John Vandenberg/createonly/1792837533.791114
2
179457
769935
2026-10-24T10:25:43Z
Pywikibot-test
23124
Pywikibot 11.10.0.dev0
769935
wikitext
text/x-wiki
phoiac9h4m842xq45sp7s6u21eteeq1
Wikipedia:Sandbox/MoveIt moved 2026-10-24T10-53-49-104Z
4
179458
769940
2026-10-24T10:53:50Z
Closed Limelike Curves
60396
MoveIt live rename test: creating the page to be renamed
769940
wikitext
text/x-wiki
Throwaway page for the Move It live rename test (2026-10-24T10-53-49-104Z). Safe to delete.
6wbsdz2i4b574vine8a387d31v62ued
769941
769940
2026-10-24T10:53:51Z
Closed Limelike Curves
60396
Closed Limelike Curves moved page [[Wikipedia:Sandbox/MoveIt move 2026-10-24T10-53-49-104Z]] to [[Wikipedia:Sandbox/MoveIt moved 2026-10-24T10-53-49-104Z]]: Move It live rename test (via Move It browser extension)
769940
wikitext
text/x-wiki
Throwaway page for the Move It live rename test (2026-10-24T10-53-49-104Z). Safe to delete.
6wbsdz2i4b574vine8a387d31v62ued
769944
769941
2026-10-24T10:53:53Z
Closed Limelike Curves
60396
MoveIt live rename test teardown
769944
wikitext
text/x-wiki
phoiac9h4m842xq45sp7s6u21eteeq1
Wikipedia:Sandbox/MoveIt move 2026-10-24T10-53-49-104Z
4
179459
769942
2026-10-24T10:53:51Z
Closed Limelike Curves
60396
Closed Limelike Curves moved page [[Wikipedia:Sandbox/MoveIt move 2026-10-24T10-53-49-104Z]] to [[Wikipedia:Sandbox/MoveIt moved 2026-10-24T10-53-49-104Z]]: Move It live rename test (via Move It browser extension)
769942
wikitext
text/x-wiki
#REDIRECT [[Wikipedia:Sandbox/MoveIt moved 2026-10-24T10-53-49-104Z]]
{{Redirect category shell|
{{R from move}}
}}
4n09n9lor8d3jjvv453euo8henm78fl
769943
769942
2026-10-24T10:53:52Z
Closed Limelike Curves
60396
MoveIt live rename test teardown
769943
wikitext
text/x-wiki
phoiac9h4m842xq45sp7s6u21eteeq1