Wikipedia
testwiki
https://test.wikipedia.org/wiki/Main_Page
MediaWiki 1.48.0-wmf.3
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
783616
783403
2026-11-08T22:20:44Z
~2026-59109-00
76606
/* Test */ Start without account
783616
wikitext
text/x-wiki
= Test =
1108
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:23, 257 days, (10:24+ to fill out).
*10:24, 10:25, 10:26, 10:27, 10:28 (?)
*10:29, 10:30, 10:31, 11:1, 11:2 (?)
*11:3 (?)
*11:4 (?)
*11:5 (11:6) (?)
''11:7''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
Current, 202 61022. ($#!+).
*202 61023. (List of Airports, In the World)
''20261024''
quh8zp2x5sokhako1qfa7t29jwwyk84
783621
783616
2026-11-08T22:30:31Z
~2026-59109-00
76606
/* Test */ Start
783621
wikitext
text/x-wiki
= Test =
1108 - Room
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:23, 257 days, (10:24+ to fill out).
*10:24, 10:25, 10:26, 10:27, 10:28 (?)
*10:29, 10:30, 10:31, 11:1, 11:2 (?)
*11:3 (?)
*11:4 (?)
*11:5 (11:6) (?)
''11:7''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
Current, 202 61022. ($#!+).
*202 61023. (List of Airports, In the World)
''20261024''
9vpds8vzr5itrqqz95zkl0k5hfl249a
783622
783621
2026-11-08T22:31:17Z
~2026-59109-00
76606
783622
wikitext
text/x-wiki
= Test =
1108 - Bedroom
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:23, 257 days, (10:24+ to fill out).
*10:24, 10:25, 10:26, 10:27, 10:28 (?)
*10:29, 10:30, 10:31, 11:1, 11:2 (?)
*11:3 (?)
*11:4 (?)
*11:5 (11:6) (?)
''11:7''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
Current, 202 61022. ($#!+).
*202 61023. (List of Airports, In the World)
''20261024''
ni4khhzpj3mwiruzdvugwsk3bmuhc3a
783623
783622
2026-11-08T22:31:41Z
~2026-59109-00
76606
783623
wikitext
text/x-wiki
= Test =
1108 - Bedroom and Bathroom House
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:23, 257 days, (10:24+ to fill out).
*10:24, 10:25, 10:26, 10:27, 10:28 (?)
*10:29, 10:30, 10:31, 11:1, 11:2 (?)
*11:3 (?)
*11:4 (?)
*11:5 (11:6) (?)
''11:7''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
Current, 202 61022. ($#!+).
*202 61023. (List of Airports, In the World)
''20261024''
70zr118njqsuybmlt0ncl598m4mx8qg
783624
783623
2026-11-08T22:32:16Z
~2026-59109-00
76606
/* Test */ 2 more days next
783624
wikitext
text/x-wiki
= Test =
1108 - Bedroom and Bathroom House
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:23, 257 days, (10:24+ to fill out).
*10:24, 10:25, 10:26, 10:27, 10:28 (?)
*10:29, 10:30, 10:31, 11:1, 11:2 (?)
*11:3 (?)
*11:4 (?)
*11:5 (?)
*11:6 (11:7) (?)
''11:8''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
Current, 202 61022. ($#!+).
*202 61023. (List of Airports, In the World)
''20261024''
t79bzwse5ip2ov9i2ryccb13szvlxvm
783625
783624
2026-11-08T22:33:11Z
~2026-59109-00
76606
/* Test */ 24
783625
wikitext
text/x-wiki
= Test =
1108 - Bedroom and Bathroom House
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:23, 257 days, (10:24+ to fill out).
*10:24, 10:25, 10:26, 10:27, 10:28 (?)
*10:29, 10:30, 10:31, 11:1, 11:2 (?)
*11:3 (?)
*11:4 (?)
*11:5 (?)
*11:6 (11:7) (?)
''11:8''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
*202 61022, Cans of Cola and Solo, Chips and Others.
Current, 202 61022. ($#!+).
*202 61023. (List of Airports, In the World)
''20261024''
ksm88frg072y6w17i1og5oodsw6io87
783626
783625
2026-11-08T22:33:42Z
~2026-59109-00
76606
/* Test */ 24 done
783626
wikitext
text/x-wiki
= Test =
1108 - Bedroom and Bathroom House
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:23, 257 days, (10:24+ to fill out).
*10:24, 10:25, 10:26, 10:27, 10:28 (?)
*10:29, 10:30, 10:31, 11:1, 11:2 (?)
*11:3 (?)
*11:4 (?)
*11:5 (?)
*11:6 (11:7) (?)
''11:8''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
*202 61022, Cans of Cola and Solo, Chips and Others.
Current, 202 61023. ($#!+).
*202 61024. (Battery Meter or Percent)
''20261025''
3gxjf4futvj8prkjz0gj8ecjlvci9bf
783627
783626
2026-11-08T22:34:38Z
~2026-59109-00
76606
/* Test */ 25
783627
wikitext
text/x-wiki
= Test =
1108 - Bedroom and Bathroom House
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:23, 257 days, (10:24+ to fill out).
*10:24, 10:25, 10:26, 10:27, 10:28 (?)
*10:29, 10:30, 10:31, 11:1, 11:2 (?)
*11:3 (?)
*11:4 (?)
*11:5 (?)
*11:6 (11:7) (?)
''11:8''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
*202 61022, Cans of Cola and Solo, Chips and Others.
*202 61023, Cans of Coca Cola, Grated Cheese, Gold Machine and others.
Current, 202 61023. ($#!+).
*202 61024. (Battery Meter or Percent)
''20261025''
j3l2c47hotwpdpncwwdio2yikcm5hfp
783628
783627
2026-11-08T22:35:08Z
~2026-59109-00
76606
/* Test */ 25 done
783628
wikitext
text/x-wiki
= Test =
1108 - Bedroom and Bathroom House
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:23, 257 days, (10:24+ to fill out).
*10:24, 10:25, 10:26, 10:27, 10:28 (?)
*10:29, 10:30, 10:31, 11:1, 11:2 (?)
*11:3 (?)
*11:4 (?)
*11:5 (?)
*11:6 (11:7) (?)
''11:8''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
*202 61022, Cans of Cola and Solo, Chips and Others.
*202 61023, Cans of Coca Cola, Grated Cheese, Gold Machine and others.
Current, 202 61024. ($#!+).
*202 61025. (Universe Decay, Protons)
''20261026''
0kjgwtqw8gvrlzgik4zayi0e1jqjsn1
783629
783628
2026-11-08T22:35:39Z
~2026-59109-00
76606
/* Test */ 2 more days
783629
wikitext
text/x-wiki
= Test =
1108 - Bedroom and Bathroom House
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:25, 262 days, (10:26+ to fill out).
*10:24, 10:25, 10:26, 10:27, 10:28 (?)
*10:29, 10:30, 10:31, 11:1, 11:2 (?)
*11:3 (?)
*11:4 (?)
*11:5 (?)
*11:6 (11:7) (?)
''11:8''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
*202 61022, Cans of Cola and Solo, Chips and Others.
*202 61023, Cans of Coca Cola, Grated Cheese, Gold Machine and others.
Current, 202 61024. ($#!+).
*202 61025. (Universe Decay, Protons)
''20261026''
7mdatredqvl7ayc7ryijzxljfw714fa
783631
783629
2026-11-08T22:36:22Z
~2026-59109-00
76606
/* Test */ All done
783631
wikitext
text/x-wiki
= Test =
1108 - Bedroom and Bathroom House
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:25, 262 days, (10:26+ to fill out).
*10:26, 10:27, 10:28, 10:29, 10:30 (?)
*10:31, 11:1, 11:2, 11:3, 11:4 (?)
*11:5 (?)
*11:6 (11:7) (?)
''11:8''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
*202 61022, Cans of Cola and Solo, Chips and Others.
*202 61023, Cans of Coca Cola, Grated Cheese, Gold Machine and others.
Current, 202 61024. ($#!+).
*202 61025. (Universe Decay, Protons)
''20261026''
4fem42byv7g1z83w7jz4ie3i720mehw
783634
783631
2026-11-08T22:40:34Z
~2026-59109-00
76606
783634
wikitext
text/x-wiki
= Test =
1108 - Bedroom and Bathroom House
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 The Account Sometimes.
Up to date
2:6 — 10:25, 262 days, (10:26+ to fill out).
*10:26, 10:27, 10:28, 10:29, 10:30 (?)
*10:31, 11:1, 11:2, 11:3, 11:4 (?)
*11:5 (?)
*11:6 (11:7) (?)
''11:8''
*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.
*202 61012, Bag, Bars and Others.
*202 61013, Baskets, Blankets, Staples and Others.
*202 61014, K-Times, Bandai, Betadine and Others.
*202 61015, Food Shopping like Chipstack, Waters and Others.
*202 61016, Plates, Container, Foil, Battery Pack and Others.
*202 61017, Milkshakes, Deep Heat, Domino's Pizza and Others.
*202 61018, Airfryer Shelf, Pillows, Slippers and Others.
*202 61019, Food Shopping like Cans of Coca Cola, Milk, Donuts and Others.
*202 61020, Baskets, Coathangers, Shapes and Others.
*202 61021, Yellow Bag, Cloth, Paper and Chargerpiece and Others.
*202 61022, Cans of Cola and Solo, Chips and Others.
*202 61023, Cans of Coca Cola, Grated Cheese, Gold Machine and others.
Current, 202 61024. ($#!+).
*202 61025. (Universe Decay, Protons)
''20261026''
lqa2fc1dunisn3vf5s6lmqppu2g1s78
User:Nardog/sandbox2.js
2
118608
783476
781143
2026-11-08T13:00:17Z
Nardog
40946
783476
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) || !this.hasChildNodes) 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-prefixindex-list > li > .allpagesredirect > 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-prefixindex-list > li > .allpagesredirect > 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));
})();
h6je9zwi4nr6bto0hk0kd6msukkh5l0
783477
783476
2026-11-08T13:00:37Z
Nardog
40946
783477
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) || !this.hasChildNodes()) 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-prefixindex-list > li > .allpagesredirect > 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-prefixindex-list > li > .allpagesredirect > 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));
})();
kbupy0zlmbgki469el57gk06t13vyc3
783478
783477
2026-11-08T13:01:48Z
Nardog
40946
783478
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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-prefixindex-list > li > .allpagesredirect > 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));
})();
m8rh6kpq5m2yjklvcuwnze96d9vp6dy
783481
783478
2026-11-08T13:31:31Z
Nardog
40946
783481
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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.title;
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-prefixindex-list > li > .allpagesredirect > 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));
})();
c83e2wavxkhfp6xwyikkp4gyjtnf2mh
783482
783481
2026-11-08T13:31:53Z
Nardog
40946
783482
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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 || link.title;
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-prefixindex-list > li > .allpagesredirect > 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));
})();
39cu5105l6wif46wsqa9k9o2afkg7gj
783483
783482
2026-11-08T13:34:06Z
Nardog
40946
783483
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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-prefixindex-list > li > .allpagesredirect > 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));
})();
pu36jvrccdti7y8jpc9q4lqc2xl4p5n
783529
783483
2026-11-08T16:27:49Z
Nardog
40946
783529
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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.splice(0, 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-prefixindex-list > li > .allpagesredirect > 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));
})();
9i24e1vy7f1dqa66bjj3npc5jozcbby
783536
783529
2026-11-08T16:51:45Z
Nardog
40946
783536
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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-prefixindex-list > li > .allpagesredirect > 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));
})();
fyb16fvyp5jctyah4s0lwfxt2e0rdrl
783564
783536
2026-11-08T19:23:16Z
Nardog
40946
783564
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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.text(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-prefixindex-list > li > .allpagesredirect > 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));
})();
qarm3mg62hg22jczqvzh5g3ft4a483m
783565
783564
2026-11-08T19:24:55Z
Nardog
40946
783565
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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) {
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = w.title;
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-prefixindex-list > li > .allpagesredirect > 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));
})();
3x1xqiw3kcyu6ne9cemef91bo9gixg3
783635
783565
2026-11-08T22:55:54Z
Nardog
40946
783635
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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) {
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = w.title;
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-prefixindex-list > li > .allpagesredirect > 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.');
}
});
mw.config.get('wgWikiID') === 'metawiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
mw.loader.using(['mediawiki.api', 'user.options'], async () => {
let watched = mw.user.options.get('userjs-watchvotes');
if (watched && Math.floor(Date.now() / 1000) - watched < 86400) return;
let watching = [], wrcontinue;
do {
let response = await new mw.Api().get({
action: 'query',
list: 'watchlistraw',
wrnamespace: 0,
wrlimit: 'max',
wrfromtitle: 'Community Wishlist/W',
wrtotitle: 'Community Wishlist/W:',
wrcontinue,
formatversion: 2
});
watching.push(...response.watchlistraw.map(({ title }) => title));
wrcontinue = response.continue?.wrcontinue;
} while (wrcontinue);
let missing = watching.filter(title => (
!/\D/.test(title.slice(20)) && !watching.includes(`${title}/Votes`)
)).map(title => `${title}/Votes`);
while (missing.length) {
await new mw.Api().watch(missing);
missing.splice(0, 50);
}
new mw.Api().saveOption('userjs-watchvotes', Math.floor(Date.now() / 1000));
});
['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));
})();
2jxpf63o58vyx25v2o2dhgc44o9wek0
783636
783635
2026-11-08T22:57:24Z
Nardog
40946
783636
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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) {
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = w.title;
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-prefixindex-list > li > .allpagesredirect > 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.');
}
});
mw.config.get('wgWikiID') === 'metawiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
mw.loader.using(['mediawiki.api', 'user.options'], async () => {
let watched = mw.user.options.get('userjs-watchvotes');
if (watched && Math.floor(Date.now() / 1000) - watched < 86400) return;
let watching = [], wrcontinue;
do {
let response = await new mw.Api().get({
action: 'query',
list: 'watchlistraw',
wrnamespace: 0,
wrlimit: 'max',
wrfromtitle: 'Community Wishlist/W',
wrtotitle: 'Community Wishlist/W:',
wrcontinue,
formatversion: 2
});
watching.push(...response.watchlistraw.map(({ title }) => title));
wrcontinue = response.continue?.wrcontinue;
} while (wrcontinue);
let missing = watching.filter(title => (
!/\D/.test(title.slice(20)) && !watching.includes(`${title}/Votes`)
)).map(title => `${title}/Votes`);
let count = missing.length;
while (missing.length) {
await new mw.Api().watch(missing);
missing.splice(0, 50);
}
new mw.Api().saveOption('userjs-watchvotes', Math.floor(Date.now() / 1000));
mw.notify(`Now watching ${count} /Votes pages.`);
});
['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));
})();
7kzorocyziqlf4gf34sh6kz9saakrmj
783637
783636
2026-11-08T23:00:21Z
Nardog
40946
783637
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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) {
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = w.title;
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-prefixindex-list > li > .allpagesredirect > 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.');
}
});
mw.config.get('wgWikiID') === 'metawiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
mw.loader.using(['mediawiki.api', 'user.options'], async () => {
let watched = mw.user.options.get('userjs-watchvotes');
if (watched && Math.floor(Date.now() / 1000) - watched < 86400) return;
let watching = [], wrcontinue;
do {
let response = await new mw.Api().get({
action: 'query',
list: 'watchlistraw',
wrnamespace: 0,
wrlimit: 'max',
wrfromtitle: 'Community Wishlist/W',
wrtotitle: 'Community Wishlist/W:',
wrcontinue,
formatversion: 2
});
watching.push(...response.watchlistraw.map(({ title }) => title));
wrcontinue = response.continue?.wrcontinue;
} while (wrcontinue);
let missing = watching
.filter(title => !/\D/.test(title.slice(20)))
.map(title => `${title}/Votes`)
.filter(title => !watching.includes(title));
let count = missing.length;
while (missing.length) {
await new mw.Api().watch(missing);
missing.splice(0, 50);
}
new mw.Api().saveOption('userjs-watchvotes', Math.floor(Date.now() / 1000));
mw.notify(`Now watching ${count} /Votes pages.`);
});
['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));
})();
qovlc7ukvehmndzfvuf7w10m5gw3w4m
783638
783637
2026-11-08T23:01:34Z
Nardog
40946
783638
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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) {
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = w.title;
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-prefixindex-list > li > .allpagesredirect > 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.');
}
});
mw.config.get('wgWikiID') === 'metawiki' &&
mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' &&
mw.loader.using(['mediawiki.api', 'user.options'], async () => {
let watched = mw.user.options.get('userjs-watchvotes');
if (watched && Math.floor(Date.now() / 1000) - watched < 86400) return;
let watching = [], wrcontinue;
do {
let response = await new mw.Api().get({
action: 'query',
list: 'watchlistraw',
wrnamespace: 0,
wrfromtitle: 'Community Wishlist/W',
wrtotitle: 'Community Wishlist/W:',
wrlimit: 'max',
wrcontinue,
formatversion: 2
});
watching.push(...response.watchlistraw.map(({ title }) => title));
wrcontinue = response.continue?.wrcontinue;
} while (wrcontinue);
let missing = watching
.filter(title => !/\D/.test(title.slice(20)))
.map(title => `${title}/Votes`)
.filter(title => !watching.includes(title));
let count = missing.length;
while (missing.length) {
await new mw.Api().watch(missing);
missing.splice(0, 50);
}
new mw.Api().saveOption('userjs-watchvotes', Math.floor(Date.now() / 1000));
mw.notify(`Now watching ${count} /Votes pages.`);
});
['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));
})();
9gx85z9nageudomb2u214p7meljv7cd
783639
783638
2026-11-08T23:05:26Z
Nardog
40946
783639
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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) {
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = w.title;
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-prefixindex-list > li > .allpagesredirect > 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.');
}
});
mw.config.get('wgWikiID') === 'metawiki' &&
Math.round(Math.random()) &&
mw.loader.using(['mediawiki.api', 'user.options'], async () => {
let watched = mw.user.options.get('userjs-watchvotes');
if (watched && Math.floor(Date.now() / 1000) - watched < 86400) return;
let watching = [], wrcontinue;
do {
let response = await new mw.Api().get({
action: 'query',
list: 'watchlistraw',
wrnamespace: 0,
wrfromtitle: 'Community Wishlist/W',
wrtotitle: 'Community Wishlist/W:',
wrlimit: 'max',
wrcontinue,
formatversion: 2
});
watching.push(...response.watchlistraw.map(({ title }) => title));
wrcontinue = response.continue?.wrcontinue;
} while (wrcontinue);
let missing = watching
.filter(title => !/\D/.test(title.slice(20)))
.map(title => `${title}/Votes`)
.filter(title => !watching.includes(title));
let count = missing.length;
while (missing.length) {
await new mw.Api().watch(missing);
missing.splice(0, 50);
}
new mw.Api().saveOption('userjs-watchvotes', Math.floor(Date.now() / 1000));
mw.notify(`Now watching ${count} /Votes pages.`);
});
['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));
})();
c3cch4noxd24y0x290wal54uyzdrhma
783640
783639
2026-11-08T23:05:56Z
Nardog
40946
783640
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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) {
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = w.title;
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-prefixindex-list > li > .allpagesredirect > 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.');
}
});
mw.config.get('wgWikiID') === 'metawiki' &&
mw.loader.using(['mediawiki.api', 'user.options'], async () => {
let watched = mw.user.options.get('userjs-watchvotes');
if (watched && Math.floor(Date.now() / 1000) - watched < 86400) return;
let watching = [], wrcontinue;
do {
let response = await new mw.Api().get({
action: 'query',
list: 'watchlistraw',
wrnamespace: 0,
wrfromtitle: 'Community Wishlist/W',
wrtotitle: 'Community Wishlist/W:',
wrlimit: 'max',
wrcontinue,
formatversion: 2
});
watching.push(...response.watchlistraw.map(({ title }) => title));
wrcontinue = response.continue?.wrcontinue;
} while (wrcontinue);
let missing = watching
.filter(title => !/\D/.test(title.slice(20)))
.map(title => `${title}/Votes`)
.filter(title => !watching.includes(title));
let count = missing.length;
while (missing.length) {
await new mw.Api().watch(missing);
missing.splice(0, 50);
}
new mw.Api().saveOption('userjs-watchvotes', Math.floor(Date.now() / 1000));
mw.notify(`Now watching ${count} /Votes pages.`);
});
['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));
})();
d6vg9zf5qqkvhhxqfgpumfjysq1y3uq
783641
783640
2026-11-08T23:06:52Z
Nardog
40946
783641
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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) {
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = w.title;
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-prefixindex-list > li > .allpagesredirect > 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.');
}
});
mw.config.get('wgWikiID') === 'metawiki' &&
mw.loader.using(['mediawiki.api', 'user.options'], async () => {
let watched = mw.user.options.get('userjs-watchvotes');
if (watched && Math.floor(Date.now() / 1000) - watched < 43200) return;
let watching = [], wrcontinue;
do {
let response = await new mw.Api().get({
action: 'query',
list: 'watchlistraw',
wrnamespace: 0,
wrfromtitle: 'Community Wishlist/W',
wrtotitle: 'Community Wishlist/W:',
wrlimit: 'max',
wrcontinue,
formatversion: 2
});
watching.push(...response.watchlistraw.map(({ title }) => title));
wrcontinue = response.continue?.wrcontinue;
} while (wrcontinue);
let missing = watching
.filter(title => !/\D/.test(title.slice(20)))
.map(title => `${title}/Votes`)
.filter(title => !watching.includes(title));
let count = missing.length;
while (missing.length) {
await new mw.Api().watch(missing);
missing.splice(0, 50);
}
new mw.Api().saveOption('userjs-watchvotes', Math.floor(Date.now() / 1000));
mw.notify(`Now watching ${count} /Votes pages.`);
});
['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));
})();
tvjuysy2q0zhns16gcco33fvat385vp
783642
783641
2026-11-08T23:07:40Z
Nardog
40946
783642
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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) {
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = w.title;
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-prefixindex-list > li > .allpagesredirect > 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.');
}
});
mw.config.get('wgWikiID') === 'metawiki' &&
mw.loader.using(['mediawiki.api', 'user.options'], async () => {
let now = Math.floor(Date.now() / 1000);
let watched = mw.user.options.get('userjs-watchvotes');
if (watched && now - watched < 43200) return;
let watching = [], wrcontinue;
do {
let response = await new mw.Api().get({
action: 'query',
list: 'watchlistraw',
wrnamespace: 0,
wrfromtitle: 'Community Wishlist/W',
wrtotitle: 'Community Wishlist/W:',
wrlimit: 'max',
wrcontinue,
formatversion: 2
});
watching.push(...response.watchlistraw.map(({ title }) => title));
wrcontinue = response.continue?.wrcontinue;
} while (wrcontinue);
let missing = watching
.filter(title => !/\D/.test(title.slice(20)))
.map(title => `${title}/Votes`)
.filter(title => !watching.includes(title));
let count = missing.length;
while (missing.length) {
await new mw.Api().watch(missing);
missing.splice(0, 50);
}
new mw.Api().saveOption('userjs-watchvotes', now);
mw.notify(`Now watching ${count} /Votes pages.`);
});
['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));
})();
06ayltphbg44x2pn0sh4o0o9sq71tyn
783653
783642
2026-11-09T11:10:41Z
Nardog
40946
783653
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) || !this.hasChildNodes()) 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, #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, .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-prefixindex-list > li > .allpagesredirect > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-changeslist-line-inner-comment > .comment > a, ' +
'.mw-changeslist-line.mw-changeslist-src-mw-categorize .mw-enhanced-rc-nested > .comment > a'
},
{
selector: '.mw-spcontent li a',
position: 'end',
titlesOnly: true
}
];
if (isEdit) {
let post = $tools => {
if (!$tools[0].closest('.templatesUsed')) return;
$tools.parent().contents().last().each(function () {
this.textContent = this.textContent.slice(1);
}).end().slice(-3, -1).remove();
};
let callback = mw.util.debounce(() => {
processLinks(
$('.mw-editfooter-list a, #wikiPreview > .previewnote a'),
{ titlesOnly: true, post }
);
}, 500);
mw.hook('wikipage.editform').add($form => {
callback();
$form.find('.templatesUsed').each(function () {
if (processed.has(this)) return;
processed.add(this);
new MutationObserver(callback)
.observe(this, { childList: true, subtree: true });
});
});
} else if (typeof pageType === 'number') {
$(() => {
processLinks($('.subpages a, .mw-redirectedfrom a, .redirectText a'), {});
});
}
mw.hook('wikipage.content').add($content => {
let titles = new Set();
let $links = $content.find('a');
modules.forEach(module => {
if (module.types && !module.types.includes(pageType)) return;
processLinks($links.filter(module.selector), module, titles);
});
getWatched(titles);
});
})();
mw.hook('listtools.ready').add(extend => {
// extend({
// name: 'talk',
// url: t => !t.isTalkPage() && t.canHaveTalkPage() && t.getTalkPage().getUrl(),
// next: 'hist'
// });
extend({
name: 'subject',
url: t => t.isTalkPage() && t.getSubjectPage().getUrl(),
next: 'hist'
});
extend({
name: 'last',
url: t => !t.missing && t.getUrl({ diff: 'cur', diffonly: 1 }),
next: 'links'
});
// extend({
// name: 'purge',
// url: t => t.getUrl({ action: 'purge' }),
// next: 'watch',
// callback: 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' &&
isc('User:Nardog/Consecudiff.js', 'en.wikipedia.org');
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
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.splice(0, 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) {
wishes.forEach(w => {
let id = w.crwtitle.match(/^Community Wishlist\/W(\d+)/)?.[1];
if (!id) return;
titles.w[id - 1] = w.title;
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-prefixindex-list > li > .allpagesredirect > 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.');
}
});
mw.config.get('wgWikiID') === 'metawiki' &&
mw.loader.using(['mediawiki.api', 'user.options'], async () => {
let now = Math.floor(Date.now() / 1000);
let watched = mw.user.options.get('userjs-watchvotes');
if (watched && now - watched < 43200) return;
let watching = [], wrcontinue;
do {
let response = await new mw.Api().get({
action: 'query',
list: 'watchlistraw',
wrnamespace: 0,
wrfromtitle: 'Community Wishlist/W',
wrtotitle: 'Community Wishlist/W:',
wrlimit: 'max',
wrcontinue,
formatversion: 2
});
watching.push(...response.watchlistraw.map(({ title }) => title));
wrcontinue = response.continue?.wrcontinue;
} while (wrcontinue);
let missing = watching
.filter(title => !/\D/.test(title.slice(20)))
.map(title => `${title}/Votes`)
.filter(title => !watching.includes(title));
let count = missing.length;
while (missing.length) {
await new mw.Api().watch(missing);
missing.splice(0, 50);
}
new mw.Api().saveOption('userjs-watchvotes', now);
if (count) {
mw.notify(`Now watching ${count} /Votes pages.`);
}
});
['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));
})();
5euj2rr2p56twutv2muz30tnbukiims
User:Nardog/sandbox5.js
2
122986
783530
767300
2026-11-08T16:29:09Z
Nardog
40946
783530
javascript
text/javascript
window.moveHistoryDialog?.close();
$('#mw-notification-area').empty();
$('head > style').filter(function () {
return this.textContent.includes('.movehistory');
}).remove();
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'mediawiki.Title', 'mediawiki.DateFormatter',
'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets',
'mediawiki.widgets.DateInputWidget', 'oojs-ui.styles.icons-interactions',
'mediawiki.interface.helpers.styles'
], require => {
mw.loader.addStyleTag(`.movehistory .oo-ui-window-body {
padding: 1em;
display: flex;
justify-content: center;
}
.movehistory form {
flex-grow: 1;
}
.movehistory .wikitable {
width: 100%;
margin-top: 0;
word-break: break-word;
}
.movehistory-date,
.movehistory-user {
white-space: nowrap;
}
.movehistory-title {
text-align: center;
}
.movehistory-comment {
vertical-align: top;
max-width: 16em;
}`);
let dialog;
let DateFormatter = require('mediawiki.DateFormatter');
let wrapDate = (s, isWiki) => {
let strs;
let formatter = isWiki ? DateFormatter.forSiteZone() : DateFormatter;
let date = new Date(s);
let dateTime = formatter.formatTimeAndDate(date);
let time = formatter.formatTime(date);
if (dateTime.startsWith(time)) {
time += dateTime.slice(time.length).replace(/^(\p{P}*).*/u, '$1');
strs = [time, dateTime.slice(time.length).trimStart()];
} else if (dateTime.endsWith(time)) {
strs = [dateTime.slice(0, -time.length).trimEnd(), time];
} else {
return [dateTime];
}
strs = strs.map(str => isWiki ? nowiki(str) : document.createTextNode(str));
strs.splice(1, 0, '<br>');
return strs;
};
let nowiki = s => s.replace(
/["&'<=>\[\]{|}]|:(?=\/\/)|_(?=_)|~(?=~~)/g,
m => `&#${m.codePointAt(0)};`
);
let articlePath = mw.config.get('wgArticlePath').replace(/\$1.*/, '');
let getPath = (pathname, search, hash) => {
let s = '';
if (pathname?.startsWith(articlePath)) {
s = decodeURIComponent(pathname.slice(articlePath.length));
} else if (search) {
let title = mw.util.getParamValue('title', search);
if (title) {
s = title;
}
}
if (hash) {
s += mw.util.percentDecodeFragment(hash);
}
return s.replaceAll('_', ' ');
};
let toWikitext = nodes => nodes.map(n => {
if (n.tagName === 'A') {
let link = n.classList.contains('extiw')
? n.title
: getPath(n.pathname, n.search, n.hash);
let text = toWikitext([...n.childNodes]);
return text === link ? `[[:${link}]]` : `[[:${link}|${text}]]`;
}
if (n.hasChildNodes) {
let clone = n.cloneNode();
clone.textContent = '$1';
return mw.format(clone.outerHTML, toWikitext([...n.childNodes]));
}
return nowiki(n.textContent);
}).join('');
let api = new mw.Api({
// userAgent: 'MoveHistory (https://en.wikipedia.org/wiki/User:Nardog/MoveHistory)'
});
let arrow = document.dir === 'rtl' ? ' ← ' : ' → ';
class MoveHistorySearch {
constructor(page, dir, since, until) {
this.$status = $('<div>').appendTo(dialog.$results.empty());
this.page = page;
this.ascending = dir === 'newer';
let sinceTs = `${since || '2005-06-25'}T00:00:00Z`;
let untilTs;
if (until) {
untilTs = until + 'T23:59:59Z';
}
this.params = {
action: 'query',
titles: page,
prop: 'revisions',
rvstart: this.ascending ? sinceTs : untilTs,
rvend: this.ascending ? untilTs : sinceTs,
rvdir: dir,
rvprop: 'sha1|timestamp|user|comment',
rvlimit: 'max',
formatversion: 2
};
this.revCount = 0;
this.candidates = [];
this.titles = {};
this.noRedirLinks = new WeakSet();
this.moves = [];
this.start();
}
start() {
this.setBusy(true);
dialog.actions.setMode('searching');
this.i = 0;
this.paused = false;
this.doNext();
}
doNext() {
if (!this.paused && this.candidates.length) {
this.loadMoves();
} else if (!this.paused && !this.complete && this.i < 4) {
this.loadRevs();
} else {
this.finish();
}
}
async loadRevs() {
this.i++;
this.setStatus(`Loading history${
this.revCount
? this.ascending
? ' after ' + this.lastDate
: ' before ' + this.firstDate
: ''
}...`);
try {
let response = await api.get(this.params);
let revs = response.query?.pages?.[0]?.revisions;
if (revs) {
this.processRevs(revs);
}
this.params.rvcontinue = response.continue?.rvcontinue;
this.complete = !this.params.rvcontinue && response.batchcomplete;
this.doNext();
} catch (e) {
let errorMsg = e?.error?.info || e;
this.finish(`Error loading revisions${errorMsg ? ': ' + errorMsg : ''}`);
console.error(e);
}
}
processRevs(revs) {
this.revCount += revs.length;
if (!this.ascending) {
revs.reverse();
}
revs.forEach(rev => {
let comp = this.lastRev;
this.lastRev = rev;
if (!rev.comment || !rev.user || !rev.sha1 || comp?.sha1 !== rev.sha1) {
return;
}
let matches = rev.comment.match(/\[\[:?([^\]]+)\]\].+?\[\[:?([^\]]+)\]\]/);
if (matches) {
rev.matches = matches.slice(1);
}
this.candidates.push(rev);
});
if (!this.ascending || !this.firstDate) {
this.firstDate = DateFormatter.formatTimeAndDate(new Date(revs[0].timestamp));
}
if (this.ascending || !this.lastDate) {
this.lastDate = DateFormatter.formatTimeAndDate(new Date(this.lastRev.timestamp));
}
}
async loadMoves() {
let rev = this.candidates[this.ascending ? 'shift' : 'pop']();
this.setStatus(`Seeing if there was a move at ${
DateFormatter.formatTimeAndDate(new Date(rev.timestamp))
}...`);
let date = Date.parse(rev.timestamp) / 1000;
try {
(await api.get({
action: 'query',
list: 'logevents',
letype: 'move',
lestart: date + 60,
leend: date,
leprop: 'details|title|user|parsedcomment',
lelimit: 'max',
formatversion: 2
})).query?.logevents?.reverse().some(le => {
if (le.user !== rev.user || !rev.comment.includes(le.title)) return;
let target = le.params?.target_title;
if (!target || !rev.comment.includes(target) || rev.matches && (
rev.matches.filter(s => s === le.title).length !== 1 ||
!rev.matches.includes(target)
)) {
return;
}
this.addMove({
date: rev.timestamp,
offset: new Date(Date.parse(rev.timestamp) + 1000)
.toISOString().slice(0, -5).replace(/\D/g, ''),
from: le.title,
to: target,
user: le.user,
comment: $.parseHTML(le.parsedcomment)
});
return true;
});
this.doNext();
} catch (e) {
let errorMsg = e?.error?.info || e;
this.finish(`Error loading moves${errorMsg ? ': ' + errorMsg : ''}`);
console.error(e);
}
}
addMove(move) {
if (!this.moves.length) {
this.lastName = this.ascending ? move.from : move.to;
this.$trail = $('<p>').append(this.makeLink(this.lastName));
this.$tbody = $('<tbody>');
this.$table = $('<table>').addClass('wikitable').append(
$('<thead>').append(
$('<tr>').append(
$('<th>').attr('rowspan', 2).text('Date'),
$('<th>').text(this.ascending ? 'From' : 'To'),
$('<th>').attr('rowspan', 2).text('Performer'),
$('<th>').attr('rowspan', 2).text('Comment')
),
$('<tr>').append(
$('<th>').text(this.ascending ? 'To' : 'From')
)
),
this.$tbody
);
this.$status.after('<hr>', this.$trail, this.$table);
}
if (this.ascending) {
if (this.lastName !== move.from) {
this.$trail.append(arrow + '?' + arrow, this.makeLink(move.from));
}
this.$trail.append(arrow, this.makeLink(move.to));
this.lastName = move.to;
} else {
if (this.lastName !== move.to) {
this.$trail.prepend(this.makeLink(move.to), arrow + '?' + arrow);
}
this.$trail.prepend(this.makeLink(move.from), arrow);
this.lastName = move.from;
}
this.$tbody.append(
$('<tr>').append(
$('<td>').attr({ class: 'movehistory-date', rowspan: 2 }).append(
$('<a>').attr({
href: mw.util.getUrl(this.page, {
action: 'history',
offset: move.offset
}),
title: 'See history up to this move',
target: '_blank'
}).append(wrapDate(move.date))
),
$('<td>').addClass('movehistory-title').append(
this.makeLink(this.ascending ? move.from : move.to)
),
$('<td>').attr({ class: 'movehistory-user', rowspan: 2 }).append(
this.makeLink('User:' + move.user, move.user, true),
'<br>',
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append(this.makeLink('User talk:' + move.user, 'talk', true)),
$('<span>').append(this.makeLink('Special:Contributions/' + move.user, 'contribs', true))
)
),
$('<td>').attr({ class: 'movehistory-comment', rowspan: 2 }).append(
$(move.comment).clone().attr('target', '_blank')
)
),
$('<tr>').append(
$('<td>').addClass('movehistory-title').append(
this.makeLink(this.ascending ? move.to : move.from)
)
)
);
dialog.setSize('large');
this.moves.push(move);
}
finish(error) {
let count = this.moves.length;
let complete = this.complete && !this.candidates.length;
this.setStatus(error || `Found ${count} move${count === 1 ? '' : 's'} in ${
this.revCount.toLocaleString('en')
} revisions${
this.revCount ? ` from ${this.firstDate} to ${this.lastDate}` : ''
}.${
complete ? '' : ' Click Continue to inspect more revisions.'
}`);
this.setBusy();
this.mode = complete
? count ? 'found' : 'notFound'
: count ? 'pausedFound' : 'paused';
dialog.actions.setMode(this.mode);
this.queryTitles();
}
setBusy(busy) {
MoveHistoryDialog.static.escapable = !busy;
dialog.$navigation.toggleClass('oo-ui-pendingElement-pending', !!busy);
}
setStatus(text) {
this.$status.text(text);
dialog.updateSize();
console.log(text);
}
makeLink(title, text, allowRedirect) {
let obj;
if (Object.hasOwn(this.titles, title)) {
obj = this.titles[title];
} else {
obj = { links: [] };
this.titles[title] = obj;
if (title === this.page) {
obj.classes = ['mw-selflink', 'selflink'];
obj.processed = true;
}
}
let params = obj.red && { action: 'edit', redlink: 1 } ||
!allowRedirect && obj.redirect && { redirect: 'no' };
let $link = $('<a>').attr({
href: mw.util.getUrl(obj.canonical || title, params),
title: obj.canonical || title,
target: '_blank'
}).addClass(obj.classes).text(text || title);
if (!allowRedirect && !obj.processed) {
this.noRedirLinks.add($link[0]);
}
if (!obj.processed) {
obj.links.push($link[0]);
}
return $link;
}
async queryTitles() {
let titles = Object.entries(this.titles)
.filter(([, v]) => !v.processed).map(([k]) => k);
while (titles.length) {
let curTitles = titles.slice(0, 50);
curTitles.forEach(title => {
this.titles[title].processed = true;
});
let { query } = await api.post({
action: 'query',
titles: curTitles,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: this.page,
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.normalized?.forEach(({ from, to }) => {
if (!Object.hasOwn(this.titles, from)) return;
let obj = this.titles[from];
obj.canonical = to;
this.titles[to] = obj;
});
query.pages?.forEach(p => {
if (!Object.hasOwn(this.titles, p.title)) return;
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;
}
});
curTitles.forEach(title => {
let obj = this.titles[title], $links;
if (obj.classes) {
$links = $(obj.links).addClass(obj.classes);
}
if (obj.redirect) {
$links = ($links || $(obj.links)).attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
this.noRedirLinks.has(this) && {
action: 'edit',
redlink: 1
}
) + 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) {
$links = ($links || $(obj.links)).attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
($links || $(obj.links)).attr('title', obj.canonical);
}
delete obj.links;
});
titles.splice(0, 50);
}
}
copyResults() {
let text = this.$trail.contents().get().map(n => (
n.tagName === 'A' ? `[[:${n.textContent}]]` : n.textContent
)).join('') + `
{| class="wikitable plainlinks"
! rowspan="2" | Date
! ${this.ascending ? 'From' : 'To'}
! rowspan="2" | Performer
! rowspan="2" | Comment
|-
! ${this.ascending ? 'To' : 'From'}
${this.moves.map(move => `|-
| rowspan="2" style="white-space: nowrap;" | [{{fullurl:${this.page}|action=history&offset=${move.offset}}} ${wrapDate(move.date, true).join('')}]
| style="text-align: center;" | ${
this.titles[this.ascending ? move.from : move.to] && this.titles[this.ascending ? move.from : move.to].redirect
? `[{{fullurl:${this.ascending ? move.from : move.to}|redirect=no}} ${this.ascending ? move.from : move.to}]`
: `[[:${this.ascending ? move.from : move.to}]]`
}
| rowspan="2" style="white-space: nowrap;" | [[User:${move.user}|${move.user}]]<br>([[User talk:${move.user}|talk]] | [[Special:Contributions/${move.user}|contribs]])
| rowspan="2" ${
move.comment.length ? 'style="vertical-align: top;" | ' + toWikitext(move.comment) : '|'
}
|-
| style="text-align: center;" | ${
this.titles[this.ascending ? move.to : move.from] && this.titles[this.ascending ? move.to : move.from].redirect
? `[{{fullurl:${this.ascending ? move.to : move.from}|redirect=no}} ${this.ascending ? move.to : move.from}]`
: `[[:${this.ascending ? move.to : move.from}]]`
}
`).join('')}|}`;
let $textarea = $('<textarea>').attr({
readonly: '',
style: 'position:fixed;top:-100%'
}).val(text).appendTo(document.body);
$textarea[0].select();
let copied;
try {
copied = document.execCommand('copy');
} catch {}
$textarea.remove();
if (copied) {
mw.notify('Copied');
} else {
mw.notify('Copy failed', { type: 'error' });
}
}
}
function MoveHistoryDialog(config) {
MoveHistoryDialog.parent.call(this, config);
this.$element.addClass('movehistory');
}
OO.inheritClass(MoveHistoryDialog, OO.ui.ProcessDialog);
MoveHistoryDialog.static.name = 'moveHistoryDialog';
MoveHistoryDialog.static.title = 'Move history';
MoveHistoryDialog.static.size = 'small';
MoveHistoryDialog.static.actions = [
{
modes: 'config',
flags: ['safe', 'close']
},
{
action: 'search',
label: 'Search',
modes: 'config',
flags: ['primary', 'progressive'],
disabled: true
},
{
action: 'goBack',
modes: ['paused', 'pausedFound', 'found', 'notFound'],
flags: ['safe', 'back']
},
{
action: 'continue',
label: 'Continue',
modes: ['paused', 'pausedFound'],
flags: ['primary', 'progressive']
},
{
action: 'pause',
label: 'Pause',
modes: 'searching',
flags: ['primary', 'destructive']
},
{
action: 'copy',
modes: ['pausedFound', 'found'],
label: 'Copy results as wikitext'
}
];
MoveHistoryDialog.prototype.initialize = function () {
MoveHistoryDialog.parent.prototype.initialize.apply(this, arguments);
let rt = mw.Title.newFromText(mw.config.get('wgRelevantPageName'));
let defaultValue = rt && rt.getNamespaceId() >= 0 ? rt.toText() : '';
this.pageInput = new mw.widgets.TitleInputWidget({
$overlay: this.$overlay,
api: api,
excludeDynamicNamespaces: true,
required: true,
showMissing: false,
value: defaultValue
}).connect(this, { change: 'updateButton' }).on('change', OO.ui.debounce(value => {
this.switchButton.setDisabled(!value || !this.pageInput.getMWTitle());
}, 250));
this.switchButton = new OO.ui.ButtonWidget({
disabled: !defaultValue,
label: 'Subject ↔ Talk'
}).on('click', () => {
let t = this.pageInput.getMWTitle();
let ot = t?.[t.isTalkPage() ? 'getSubjectPage' : 'getTalkPage']();
if (!ot) return;
this.pageInput.setValue(ot.toText());
});
this.directionInput = new OO.ui.RadioSelectInputWidget({
options: [
{ data: 'newer', label: 'Oldest first' },
{ data: 'older', label: 'Newest first' }
]
});
this.sinceInput = new mw.widgets.DateInputWidget({
$overlay: this.$overlay,
displayFormat: 'YYYY-MM-DD'
}).connect(this, { flag: 'updateButton' });
this.untilInput = new mw.widgets.DateInputWidget({
$overlay: this.$overlay,
displayFormat: 'YYYY-MM-DD',
mustBeAfter: '2005-06-24'
}).on('change', () => {
let m = this.untilInput.getMoment();
this.sinceInput.mustBeBefore = m.isValid() ? m.add(1, 'days') : undefined;
this.sinceInput.setValidityFlag();
}).connect(this, { flag: 'updateButton' });
this.form = new OO.ui.FormLayout({
items: [
new OO.ui.FieldLayout(this.pageInput, {
label: 'Page:',
align: 'top'
}),
this.switchButton,
new OO.ui.FieldLayout(this.directionInput, {
label: 'Direction:',
align: 'top'
}),
new OO.ui.FieldLayout(this.sinceInput, {
label: 'Since:',
align: 'top'
}),
new OO.ui.FieldLayout(this.untilInput, {
label: 'Until:',
align: 'top'
})
]
}).connect(this, { submit: ['executeAction', 'search'] });
this.$results = $('<div>');
this.form.$element
.append($('<input>').attr({ type: 'submit', hidden: '' }))
.appendTo(this.$body);
};
MoveHistoryDialog.prototype.updateButton = function () {
this.actions.setAbilities({ search: this.canSearch() });
};
MoveHistoryDialog.prototype.canSearch = function () {
return !!this.pageInput.getValue() &&
!this.sinceInput.hasFlag('invalid') &&
!this.untilInput.hasFlag('invalid');
};
MoveHistoryDialog.prototype.getSetupProcess = function (data) {
return MoveHistoryDialog.super.prototype.getSetupProcess.call(this, data).next(function () {
this.updateButton();
this.actions.setMode('config');
}, this);
};
MoveHistoryDialog.prototype.getReadyProcess = function (data) {
return MoveHistoryDialog.super.prototype.getReadyProcess.call(this, data).next(function () {
this.pageInput.focus();
}, this);
};
MoveHistoryDialog.prototype.getActionProcess = function (action) {
if (action === 'search') {
if (!this.canSearch()) return;
let config = [
this.pageInput.getMWTitle().toText(),
this.directionInput.getValue(),
this.sinceInput.getValue(),
this.untilInput.getValue()
];
if (!this.config || config.some((v, i) => v !== this.config[i])) {
this.config = config;
this.search = new MoveHistorySearch(...config);
} else {
this.actions.setMode(this.search.mode);
}
this.form.toggle(false).$element.after(this.$results);
this.setSize(this.search.moves.length ? 'large' : 'medium');
} else if (action === 'continue') {
this.search.start();
} else if (action === 'pause') {
this.search.paused = true;
} else if (action === 'copy') {
this.search.copyResults();
} else {
this.actions.setMode('config');
this.$results.detach();
this.form.toggle(true);
this.setSize('small');
}
return MoveHistoryDialog.super.prototype.getActionProcess.call(this, action);
};
dialog = new MoveHistoryDialog();
window.moveHistoryDialog = dialog;
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
dialog.open();
});
bq1860d8dt5341pflqbf98dezlu87qk
User:SongVĩ.Bot II
2
124239
783539
783341
2026-11-08T17:00:30Z
SongVĩ.Bot II
52414
[[User:SongVĩ.Bot II|Task 0]]: Đã 1777 ngày...
783539
wikitext
text/x-wiki
Cập nhật lần cuối: 09-11-2026
Đã 1777 ngày...
s4geiv2uffvcnzos4dmrllwp462wlya
Shovelware
0
148133
783643
690301
2026-11-09T00:05:07Z
InternetArchiveBot
34092
Rescuing 1 sources and tagging 0 as dead. #IABot (v2.0.9.7)
783643
wikitext
text/x-wiki
[[File:Shovelwarenew.jpg|thumb|312x312px|Good thing we have a shovel to bury these games where they belong, in a specially marked chest underground where no one will find them.]]
'''Shovelware games''' are video games that are made with a low budget and possibly little time, resulting in poor quality, terrible programming, numerous flaws such as crashing frequently, and possibly lots of [[Game glitches|glitches]] as well. Shovelware developers have a "'''quantity over quality'''" mentality, so rather than working hard on making a good or at least average game, they insist on throwing out as much games as possible to make a quick buck on platforms. Shovelware is labeled as the tool used to quickly dig rocks out of the ground. Usually, shovelware titles are made for licensed franchises such as television shows, movies, toys and books to sell more copies based on brand recognition alone. These games are often at a lower price than other AAA games and are often found in bargain bins.
Normally, each gaming generation has a specific "shovelware target" platform where most shovelware games end at often because it's the easiest platform to release games for, such as the [https://web.archive.org/web/20230105073922/https://awesomegames.miraheze.org/wiki/Atari_2600 Atari 2600], [https://web.archive.org/web/20230105073552/https://awesomegames.miraheze.org/wiki/Nintendo_Entertainment_System Nintendo Entertainment System], [https://web.archive.org/web/20230105073553/https://awesomegames.miraheze.org/wiki/PlayStation PlayStation], [https://web.archive.org/web/20230105073551/https://awesomegames.miraheze.org/wiki/PlayStation_2 PlayStation 2], [https://web.archive.org/web/20230105073551/https://awesomegames.miraheze.org/wiki/Wii Wii], currently [https://web.archive.org/web/20230105073551/https://awesomegames.miraheze.org/wiki/PlayStation_4 PS4], [https://web.archive.org/web/20230105073553/https://awesomegames.miraheze.org/wiki/Nintendo_DS DS], [https://web.archive.org/web/20230105073550/https://awesomegames.miraheze.org/wiki/Game_Boy_Color Game Boy Color] and [https://web.archive.org/web/20230105073553/https://awesomegames.miraheze.org/wiki/Nintendo_Switch Nintendo Switch] (mostly on the eShop as well). [https://web.archive.org/web/20230105073555/https://awesomegames.miraheze.org/wiki/Steam Steam], in particular, has become arguably became the worst offender of shovelware target platforms due to its complete lack of quality control (and attempts at quality control like [[Steam Greenlight|Greenlight]] and [[Steam Direct|Direct]] backfiring).
A slightly different but arguably just as crappy form of shovelware also existed back in the 1990s, thanks to the advent and large storage capacities of CDs, it was common for companies to cram in as many old and cheap to license floppy disk games on a single one as possible and sell it as "100 games for the price of one!". Many of them often being otherwise free shareware games.Test123.
== Examples ==
# [[Data Design Interactive]] released a game of such term, titled ''[[Data Design Interactive platform games|Ninjabread Man]]'', released for the PS2, Microsoft Windows and the Wii. A day later, a clone of the game, ''Anubis II'', was released. More games like ''Myth Makers: Trixie in Toyland'', ''Rock 'N' Roll Adventures'', ''Action Girlz Racing'', ''Kawasaki Jet Ski'' and others were released some years later for the PS2 and Wii.
# [[Digital Homicide Studios]] flooded Steam Greenlight with tons of shovelware with the intention of farming Steam Trading Cards, often putting countless amounts of [[reskins]] of the same game over and over. At one point they had over '''''100''''' entries on Steam Greenlight.
# The practice of [[asset flipping]] allows people to easily produce massive amounts of shovelware by copy-pasting pre-made assets into free-to-use game engines, which can then be released with little trouble on Steam. Nowadays, Steam is infamous for the countless amount of shovelware overshadowing real quality games. This has gone to the point that now many indie developers are abandoning Steam and because it's "too crowded". Most of those games are made with Unity, an easy-to-use game engine that contains an asset store.
# [[Generic RPG Maker games]] are the RPG Maker equivalent of shovelware, and due to Steam Greenlight has caused these games to degrade RPG Maker's reputation. Games such as Raywin and Arcane Raise are examples of this.
# [[Generic hentai shovelware games]] are the Hentai equivalent of shovelware. They flooded Steam after Valve announced they would getting rid of restrictions regarding adult video games. These "games" would eventually overshadow genuine adult video games as much as ordinary shovelware does to normal games and consoles.
# The PS4 version of the PS Store was filled with tons of shovelware. In fact, the PlayStation 4's PlayStation Store quality control is so poor that it makes Steam Greenlight look '''tolerable'''. Hell, even Sony promotes said games!
# Each of Nintendo's portable systems had shovelware games released due to their successes and popularity, from the Game Boy to the Nintendo 3DS. A good chunk of these are usually based on movies or television shows that were popular at the time of their release.
# Some [[Mobile games on mainstream platforms|mobile games are ported on mainstream platforms]], but they often end up being even worse on these platforms mostly because they're either shovelware, they aren't optimized for game controllers or computer accessories, or they have '''no changes''' (not even the controls) whatsoever. However, some of these ports have worked well and eliminate the need for an Android emulator like the highly demanding BlueStacks.
# The Wii had tons of shovelware games released for it due to the console's popularity with casual audiences and the console's innovative motion-based control scheme.
# [[Steam Direct]] has made the shovelware problems on Steam that it was supposed to fix the quality control but it was even worse than they were before because '''anyone''' can publish their game on Steam Direct.
# Because of the popularity of mobile gaming, major mobile game stores such as the App Store became easy targets of shovelware. Developers like [[Voodoo]] especially use the "''hyper-casual''" genre as an excuse to publish many lazily-made games that often have heavy lag, tons of ads, pay-to-win mechanics and overly simple gameplay. And because the [[Google Play|Google Play Store]] completely lacks quality control, it is the worst offender out of all mobile game stores, having rip-offs of other games and bootlegs, and even applications that are '''outright malware'''.
# Indie game platforms like [[Game Jolt]] and Itch.io have a ton of shovelware, asset flips, and games that look like rejected Steam Greenlight titles.
# Although there are still many awesome games on the site, the ''[https://web.archive.org/web/20230105072031/https://awesomegames.miraheze.org/wiki/Roblox Roblox]'' game section is filled with countless shovelware, including but not limited to: [https://web.archive.org/web/20230105073551/https://garbagerobloxgames.miraheze.org/wiki/Obbies Obbies], Tycoons, Cafes and even rip-offs of popular games. As it's free to make and publish places and because there is barely any quality control, it's simple for bad developers to make a quick buck. In fact, there's too much shovelware that there is even a group dedicated to that, named Shovelware Obbies, but the budget ironically seems higher than a typical Shovelware game.
# ''[[SpongeBob SquarePants: Plankton's Robotic Revenge]]'' and ''[[SpongeBob HeroPants]]'' both developed by Behaviour Interactive and published by Activision were both critically panned video games based off the ''SpongeBob SquarePants'' franchise. Both games were criticized for being repetitive level design, boring gameplay, having poor graphics compared to its CGI cutscenes, and lame plot, both games were also rip-offs of ''Ratchet and Clank: All 4 One''. Luckily, THQ Nordic re-acquired the ''SpongeBob'' license and with it, recently released the fan-acclaimed ''Battle for Bikini Bottom Rehydrated''.
# [[BAM! Entertainment]] is definitely no stranger to producing shovelware titles such as ''[[Dexter's Laboratory: Robot Rampage]]'' and ''[[The Powerpuff Girls: Paint the Townsville Green]]''. They are mostly known to producing shovelware Cartoon Network game titles like the ones mentioned in this reason right here.
# [[Acclaim Entertainment]] is also one of the main examples of shovelware, but mostly in the 8-bit and 16-bit era, many of their games published such as ''Total Recall'', ''Dragon: The Bruce Lee Story'', ''Krusty's Fun House''. are mostly known for publishing shovelware titles based on popular comics, television series and movies like the ones mentioned.
# Adobe Flash used to be the holy grail of the shovelware, as anybody could create a game and publish it for free. However, it was more collaborative trend to share something with the community than a media to lure and generate shady incomes. For example, Newgrounds and Kongregate.
# Sports games published by [[Head Games Publishing]] under the ''[[:Category:Extreme Sports Series games|Extreme]]'' banner became infamous for their exceptionally low quality of production. The games were horribly rushed, especially ''[[Extreme Paintbrawl]]'', where the production process '''only took two weeks'''. ''Paintbrawl'' was considered '''one of the worst games of all time''' for a reason, but somehow got sequels. The most notable games under this brand are ''Extreme Paintbrawl'' with [[Extreme Paintbrawl 2|its sequels]], ''[[Extreme Boards & Blades]]'', ''[[Extreme Wintersports]]'', ''Extreme Bullrider'', and ''Extreme Rock Climbing''.
# While the [[AAA game industry]] is made from games/developers with much higher budget than shovelware games/developers ever had, this doesn't mean AAA gaming industry is stranger to shovelware. Some games/game series technically fit into (at least) some aspects of the definition of "shovelware", with the worst offender being the [[:Category:NFL/Madden games|''Madden NFL'']] series (WTS #1,#5 and #15 and #18 fits into 2017 and above at a large extent).
# There are some companies in the indie game industry that develop horror games that one releases a successful game, and the other fails. Examples of horror games that classify as shovelware are squalid ''Resident Evil'' rip-offs, for example the ''Outbreak'' series by Dead Drop Studios and ''Dawn of Fear'' by Brok3nsite.
# Like the Wii, the [[V.Smile]] has a ton of shovelware games.
==Why They Suck ==
# They're usually filled with lots of glitches such as mechanics being broken or not working because they were not programmed or terribly coded, players clipping through floors, infinite loading screens, framerate issues, and frequent crashes.
# Some shovelware games have boring gameplay such as repetitive level design, the games taking too long for the player to get control.
# Sometimes the gameplay would be cloned/ripped off from other games just for a quick buck, except much worse as they will plagiarize code and parts of the game from the source material. ''Mole Kart'' is an example of this tactic.
# Developers will prefer "quantity over quality", as shovelware developers would release several games in one year alone, two of the best examples being [[Digital Homicide]] and [[Silicon Echo Studios]].
# Sometimes they'd release the game [[Ninjabread Man|over]] and over but change the textures and sometimes, '''[[Dalmatians|only the title is changed]].'''
# Some games are usually [[Snow White and the 7 Clever Boys|bootleg games that are based on movies]], this is more common in mobile games nowadays.
# Not only movies, though. There are even games that capitalise on real life brands (especially car brands like Lamborghini in the case of racing games), popular cities around the world, or even more recently, Internet memes and trends.
# A lot of them would create [[False advertising in games|false advertisements]], by using fake screenshots instead of the actual gameplay, this is more common in mobile games and Steam, though.
# Sometimes, these games are overpriced, despite costing budget price or not for small and/or repetitive actual content.
# Because of the sheer amount of shovelware games the developers would release, they overshadow decent games and fill a console with their shovelware, like what happened with the Nintendo Wii, Atari 2600, and Steam.
# It is one of the factors that led to [[North American video game crash]]. With no quality control at all, many companies tried to take advantage of the video game craze (even the ''Kool-Aid'' company made a game). Many of these games were abysmal in quality causing gamers to lose faith in the industry.
# Sometimes the game discs would even be poorly made that they're either released unplayable or stop working easily. Such as Phoenix Games' discs as they would get hot easily and the console making very loud noises when reading the discs. Once the discs become hot, they will be warped and comes out as unusable permanently.
# Almost always, shovelware developers will consider [[Developers against criticism|criticism as "harassment"]], and some would [[Digital Homicide's lawsuits|even go so far as to file actual lawsuits against critics]] just because they gave their games negative reviews. Some of them even release their games through Early Access to avoid criticism.
# Sometimes, these games are just [[Rushing game development|unfinished]], steal assets from other games and companies without permission or even [[Asset flipping|asset flips]].
# Many of them tend to be licensed games that rely purely on being based on popular franchises so parents who don't know any better will buy them for their kids.
# Terrible graphics such as washed out colors, and look like they came from a previous generation.
# Most developers make tons of them to make a quick buck.
#* And to add insult to injury, this actually leads to many problems as mentioned in some of the reasons above when they rush games to meet their deadline.
# A lot of times, they're only made to cash in on the popularity of a particular intellectual property, even intellectual properties that don't aren't successful or have mixed-to-negative reception in their media of origin.
# Some shovelwares are simply games that were pirated from a legit developer, and cloned thousand of times, for example using decompiler (APK Tool is infamous for Android) or resource editors it is possible to take the code of somebody else, change the author and some assets, and publish the game to the store. However, they use the tactic of hit and run and mostly hit or miss.
# Some shovelware developers are depending on microtransactions a lot and some examples of it are straight up abysmal. For example, in a RADAL video, an Indian GTA ripoff wants '''115 euros (139.52 USD) to "unlock all levels and guns".''' For whose do not use "traditional" microtransactions, there are Steam trading cards so developers are inclined to make games to farm "Steam Trading Cards" in overall.
==How to avoid shovelware games==
*Always check out '''reliable''' game reviews. If a game has poor reviews, or is a port of a mobile game (though this doesn't ''necessarily'' mean that game is bad, but be careful), avoid buying it at all costs. This is good for parents, as it would prevent them from buying a bad game for their children and wasting their money. Avoid most game journalists sites though, [[Corruption in game journalism|because most are heavily corrupt]].
*If you’re not a parent, but see one buying a game that you know is bad, the recommended method is to persuade them not buy the game and instead buy a better or average quality game.
* Don’t always trust false advertising. Games like ''Guns of Glory'', [[Lords Mobile|''Lords Mobile'']] and other ones such as most mobile game developers use that trick to make people download it. If you have young kids, be extra careful, as they can also buy special currency with your money without your acknowledge and permission.
*Many of these games can be found in stores that don't really cater to video games like Fleet Farm. While you can sometimes find a decent game, such an event is very rare so it's best to avoid these spots at all costs. Wire mesh discount bins in supermarkets are a particularly common place to find shovelware, as the supermarket usually gets these games cheap as distressed inventory from local game stores.
** On digital platforms, if the game has a low price around $4 or tends to receive very high discounts, it's likely shovelware.
==Videos==
===Extra Credit's documentary ===
<youtube>E7lD97BxMN0</youtube>
===Scott The Woz's ''Shovelware Variety Hour'' series===
<youtube width="384" height="216">M6hjqRHgLpA</youtube> <youtube width="384" height="216">m9iYj9wUrCw</youtube> <youtube width="384" height="216">33ebUw4I2N4</youtube>
==Comments==
<comments />
[[Category:Shovelware games|*]]
[[Category:Shovelware companies|*]]
[[Category:Scott The Woz episodes]]
jdmog202rt40k4gwb0oiiolt5zt11hu
User:Nardog/sandbox6.js
2
148664
783480
769116
2026-11-08T13:27:25Z
Nardog
40946
783480
javascript
text/javascript
/* globals ve */
window.refRenamerDialog?.close();
$('#mw-notification-area').empty();
$('head > style').filter(function () {
return this.textContent.includes('.refrenamer');
}).remove();
(() => {
let messages = Object.assign({
loadingSource: 'Loading the source...',
loadingHtml: 'Loading HTML...',
parsing: 'Parsing wikitext...',
opening: 'Opening the diff...',
continue: 'Continue',
main: 'Main fallback stack:',
lastName: 'Last name',
firstName: 'First name',
author: 'Author',
periodical: 'Periodical/website',
publisher: 'Publisher',
article: 'Article',
book: 'Book',
domain: 'Domain',
firstPhrase: 'First phrase',
lowercase: 'Lowercase',
removeDia: 'Remove diacritics',
removePunct: 'Remove punctuation',
replaceSpace: 'Replace space with:',
year: 'Year',
yearFallback: 'Fall back on any 4-digit number',
yearConvert: 'Convert to ASCII',
latinIncrement: 'Append Latin letters on collision',
increment: 'Collision resolution:',
incrementExample: 'Example',
incrementExamples: '$1, $2...',
delimiter: 'Delimiter:',
delimitConditional: 'Insert delimiters only after numerals',
removeUnreused: 'Remove unreused names',
apply: 'Apply',
reset: 'Reset',
tableName: 'Name',
tableCaption: 'References to rename',
tableRef: 'Reference',
tableNewName: 'New name',
tableAddRemove: '±',
reapplyTooltip: 'Reapply current options',
propsTooltip: 'View/insert properties',
keepTooltip: 'Uncheck to remove',
tableRemove: '(Remove)',
removeTooltip: 'Remove from references to rename',
otherTableCaption: 'Other named references',
notReused: '(not reused)',
expand: 'Expand',
collapse: 'Collapse',
addTooltip: 'Add to references to rename',
addAll: 'Add all',
resetSelection: 'Reset selection',
uneditableAlert: 'You do not appear to be able to edit this page.',
noNamesAlert: 'The source does not contain ref names to rename.',
noChangesError: 'No names have been modified.',
numericError: 'The following names are invalid as they consist only of numerals:',
duplicatesError: 'The following names are already used or input more than once:',
templatesWarn: 'Ref names in the following templates will not be replaced:',
invalidWarn: 'The following names have been ignored because it could not be determined which references correspond to them:',
summary: 'Replaced [[$1|VE ref names]] using [[$2|RefRenamer]]',
genericSummary: 'Renamed references using [[$1|RefRenamer]]',
summarySeparator: '; '
}, window.refrenamerMessages);
let getMsg = (key, ...args) => (
Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key
);
let notif;
let notify = async key => {
notif = await mw.notify(getMsg(key), {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
};
let dialog;
class Ref {
constructor(name, normalized) {
this.name = name;
this.names = new Set().add(name);
this.normalized = normalized;
this.isVe = /^(?::|(?:Book|Journal|News|Reference|Website)-)\d+$/.test(name);
this.isAuto = this.isVe || /^(?:auto(?:generated)?\d*|Reference[A-Z]+)$/.test(name);
}
initProps() {
this.props = {};
let coinsSpan = this.$ref[0].querySelector('.Z3988');
if (coinsSpan) {
new URLSearchParams(coinsSpan.title).forEach((v, k) => {
if (k.startsWith('rft.')) {
this.props[k.slice(4)] = v;
} else if (k === 'rft_id') {
if (/^https?:/.test(v)) {
if (this.props.domain) return;
try {
let url = new URL(v);
this.props.domain = url.hostname;
} catch {}
} else {
let match = v.match(/^info:([^/]+)\/(.+)$/);
if (match) {
this.props[match[1]] = match[2];
}
}
}
});
}
let text = this.$ref.text();
if (this.props.date) {
let numbers = this.props.date.match(/\p{Nd}+/gu);
if (numbers) {
let converted = numbers.map(n => toAscii(n));
let year = String(Math.max(...converted));
let original = numbers[converted.indexOf(year)];
this.props.year = original;
if (original !== year) {
this.props.yearAscii = year;
}
}
} else {
let match = text.match(/(?:^|\P{Nd})(\p{Nd}{4})(?!\p{Nd})/u);
if (match) {
this.props.textYear = match[1];
let ascii = toAscii(match[1]);
if (ascii !== match[1]) {
this.props.textYearAscii = ascii;
}
}
}
let link = this.$ref[0].querySelector('a.external');
if (link && link.hostname !== this.props.domain) {
this.props.linkDomain = link.hostname;
}
let match = text.match(/[^\s\p{P}].*?(?=\s*(?:\p{P}|$))/u);
if (match) {
this.props.phrase = match[0];
}
Object.freeze(this.props);
}
initRows() {
let rowClass = !this.reused && 'refrenamer-unreused';
this.$row = $('<tr>').addClass(rowClass).appendTo(dialog.$tbody);
this.$otherRow = $('<tr>').addClass(rowClass).appendTo(dialog.$otherTbody);
this.nameCell = $('<td>').addClass('refrenamer-name')
.append($('<span>').text(this.name))[0];
this.refCell = $('<td>').addClass('refrenamer-ref mw-parser-output')
.append(this.$ref)[0];
this.moveButton = new OO.ui.ButtonWidget({
framed: false,
invisibleLabel: true
}).connect(dialog, { click: ['toggleActive', this] });
this.moveCell = $('<td>').addClass('refrenamer-addremove')
.append(this.moveButton.$element)[0];
}
setActive(active) {
if (active === this.active) return;
this.active = active;
this[active ? '$otherRow' : '$row'].addClass('refrenamer-hidden');
let tooltip = getMsg(active ? 'removeTooltip' : 'addTooltip');
this.moveButton
.setFlags({ destructive: active, progressive: !active })
.setIcon(active ? 'subtract' : 'add')
.setLabel(tooltip).setTitle(tooltip);
this[active ? 'initInput' : 'initToggle']();
this[active ? '$row' : '$otherRow']
.prepend(this.nameCell, this.refCell)
.append(this.moveCell)
.removeClass('refrenamer-hidden');
}
initInput() {
if (this.input) return;
this.input = new OO.ui.MultilineTextInputWidget({
allowLinebreaks: false,
placeholder: this.name,
spellcheck: false
}).connect(dialog, { enter: ['executeAction', 'continue'] });
this.reapplyButton = new OO.ui.ButtonWidget({
classes: ['refrenamer-reapplybutton'],
framed: false,
icon: 'undo',
invisibleLabel: true,
label: getMsg('reapplyTooltip')
}).connect(dialog, {
click: ['applyConfig', [this], true]
}).connect(this.input, { click: 'focus' });
this.propsButton = new OO.ui.ButtonMenuSelectWidget({
classes: ['refrenamer-propsbutton'],
clearOnSelect: true,
framed: false,
icon: 'downTriangle',
invisibleLabel: true,
label: getMsg('propsTooltip'),
menu: {
$floatableContainer: this.input.$element,
horizontalPosition: 'end',
width: '16em'
}
});
this.propsButton.getMenu().addItems(
Object.entries(this.props).map(([k, v]) => {
let option = new OO.ui.MenuOptionWidget({
label: v,
title: v
});
option.$label.attr('data-refrenamer', k);
return option;
})
).connect(this, { choose: 'onPropChoose' });
if (!this.reused) {
this.keepCheck = new OO.ui.CheckboxInputWidget({
classes: ['refrenamer-keepcheck'],
invisibleLabel: true,
label: getMsg('keepTooltip')
}).connect(this, { change: 'onKeepChange' });
}
$('<td>').addClass('refrenamer-newname').append(
this.keepCheck?.$element,
this.input.$element,
this.reapplyButton.$element,
this.propsButton.$element
).appendTo(this.$row);
}
initToggle() {
if (this.$toggle) return;
this.$toggle = $('<button>').attr({
class: 'refrenamer-toggle oo-ui-icon-expand',
title: getMsg('expand')
}).on('click', onToggle).prependTo(this.refCell);
}
onPropChoose(item) {
this.input.insertContent(item.getTitle());
}
onKeepChange(selected) {
this.$row.toggleClass('refrenamer-kept', selected);
if (selected && !this.input.getValue()) {
dialog.applyConfig([this], true);
}
}
setupCollapsible(width) {
if (this.collapsible && Math.abs(width - this.refWidth) < 10) return;
this.refCell.classList.remove('refrenamer-collapsible');
this.collapsible = this.$ref[0].scrollHeight > this.$ref[0].clientHeight;
this.refCell.classList.toggle('refrenamer-collapsible', this.collapsible);
this.refWidth = width;
}
}
window.refRenamer = async () => {
let encodedPn = encodeURIComponent(mw.config.get('wgPageName'));
let headers = {
// 'Api-User-Agent': 'RefRenamer (https://en.wikipedia.org/wiki/User:Nardog/RefRenamer)'
};
let dependencies = [
'mediawiki.api', 'mediawiki.storage', 'mediawiki.Title',
'oojs-ui-windows', 'oojs-ui-widgets', 'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-editing-core'
];
let data = {
isEdit: !!document.getElementById('wpTextbox1') &&
!$('input[name="wpSection"]').val(),
refs: [],
templates: new Set(),
invalid: new Set()
};
try {
if (data.isEdit) {
dependencies.push('jquery.textSelection');
} else {
notify('loadingSource');
let response = await $.ajax(
mw.util.getUrl(null, { action: 'edit', venoscript: 1 }),
{ headers }
);
data.$form = $($($.parseHTML(response)).find('#editform').remove()[0]);
if (!data.$form.length) {
throw Error(getMsg('uneditableAlert'));
}
data.wikitext = data.$form.find('#wpTextbox1').val();
}
await mw.loader.using(dependencies);
if (data.isEdit) {
data.wikitext = $('#wpTextbox1').textSelection('getContents');
}
let wikitext = data.wikitext.replace(/<!--[^]*?-->/g, '');
for (let match of wikitext.matchAll(
/<ref\s+(?:name|follow)\s*=\s*(?:"\s*([^\n"]+?)\s*"?|'\s*([^\n']+?)\s*'?|([^\s>]+?))(?:\s*details\s*=\s*(?:"\s*[^\n"]+?\s*"?|'\s*[^\n']+?\s*'?|[^\s>]+?))?\s*\/?>/gi
)) {
let name = match[1] || match[2] || match[3];
let normalized = normalize(name);
let ref = data.refs.find(r => r.normalized === normalized);
if (ref) {
ref.reused = true;
ref.names.add(name);
} else {
data.refs.push(new Ref(name, normalized));
}
}
if (!data.refs.length) {
throw Error(getMsg('noNamesAlert'));
}
let response;
if (data.isEdit) {
notify('parsing');
response = await new mw.Rest().post(
'/v1/transform/wikitext/to/html/' + encodedPn,
{ wikitext: data.wikitext, body_only: true },
headers
);
} else {
notify('loadingHtml');
response = await new mw.Rest()
.get(`/v1/page/${encodedPn}/html`, null, headers);
}
let numbers = new Set();
let $page = $($.parseHTML(response));
$page.find(
'.mw-references:not([data-mw-group]) .mw-reference-text'
).each(function () {
let match = this.id.match(/^mw-reference-text-cite_note-(.+)-(\d+)$/);
if (!match) return;
let ref = data.refs.find(r => r.normalized === match[1]);
if (!ref) return;
if (ref.$ref) {
data.invalid.add(ref.name);
ref.invalid = true;
return;
}
ref.$ref = $(this);
ref.reused = ref.reused || (
ref.$ref.prev('span[rel="mw:referencedBy"]').children().length > 1
);
ref.$ref.remove().find('[id], [about]').addBack().removeAttr('id about');
ref.$ref.find('a').attr('target', '_blank')
.filter('[href^="./"]').attr('href', (_, href) => (
mw.format(mw.config.get('wgArticlePath'), href.slice(2))
));
numbers.add(match[2]);
});
let tempRe = new RegExp(`^\\./${
mw.util.escapeRegExp(mw.config.get('wgFormattedNamespaces')[10])
}:`);
$page.find(
'.mw-ref[typeof~="mw:Transclusion"], [typeof~="mw:Transclusion"] .mw-ref'
).filter(function () {
if (!/^\[\d+\]$/.test(this.textContent)) return;
let match = this.id.match(/_(\d+)-\d+$/);
return match && numbers.has(match[1]);
}).closest('[typeof~="mw:Transclusion"]').each(function () {
try {
data.templates.add(
JSON.parse(this.dataset.mw).parts[0].template.target.href
.replace(tempRe, '')
);
} catch {}
});
data.refs = data.refs.filter(ref => ref.$ref && !ref.invalid);
if (!data.refs.length) {
throw Error(getMsg('noNamesAlert'));
}
let collator;
try {
collator = Intl.Collator(mw.config.get('wgContentLanguage') + '-u-kn-true');
} catch {
collator = Intl.Collator('en-u-kn-true');
}
data.refs.sort((a, b) => collator.compare(a.name, b.name));
if (!dialog) {
initDialog();
}
dialog.open(data);
} catch (error) {
OO.ui.alert(error.message || error.responseText || error);
} finally {
if (notif) {
notif.close();
notif = null;
}
}
};
let initDialog = () => {
let rtl = document.dir === 'rtl';
let left = rtl ? 'right' : 'left';
let right = rtl ? 'left' : 'right';
mw.loader.addStyleTag(`.refrenamer .oo-ui-tabOptionWidget > .oo-ui-labelElement-label::after {
content: " (" attr(data-refrenamer) ")";
}
.refrenamer-split {
columns: 2;
}
.refrenamer-split .oo-ui-fieldLayout {
break-inside: avoid;
}
.refrenamer-split,
.refrenamer .oo-ui-layout.oo-ui-labelElement:nth-child(n+2),
.refrenamer .oo-ui-fieldLayout-align-inline .oo-ui-labelElement-label + * {
margin: 4px 0;
}
.refrenamer .oo-ui-textInputWidget > .oo-ui-inputWidget-input {
font-family: monospace,monospace;
}
.refrenamer .wikitable {
margin-bottom: 0;
overflow-wrap: break-word;
width: 100%;
}
.refrenamer-maintable .refrenamer-name,
.refrenamer .mw-reference-text,
.refrenamer-othertable .refrenamer-name::after {
font-size: 90%;
}
.refrenamer-unreused > .refrenamer-name::after {
content: "${getMsg('notReused')}";
display: inline-block;
}
.refrenamer-unreused > .refrenamer-name > span::after {
content: " ";
}
.refrenamer-hidden,
.refrenamer-unreused:not(.refrenamer-kept) > .refrenamer-newname > :not(.refrenamer-keepcheck),
.refrenamer-maintable .refrenamer-toggle,
:not(.refrenamer-collapsible) > .refrenamer-toggle {
display: none;
}
.refrenamer-name,
.refrenamer-ref {
vertical-align: top;
}
.refrenamer-maintable .refrenamer-name {
max-width: 6em;
}
.refrenamer-ref {
line-height: normal;
}
.refrenamer-newname {
height: 3em;
position: relative;
width: 12em;
}
.refrenamer-unreused:not(.refrenamer-kept) > .refrenamer-newname::after {
content: "${getMsg('tableRemove')}";
}
.refrenamer-newname > .oo-ui-textInputWidget,
.refrenamer-addremove > .oo-ui-buttonElement-frameless.oo-ui-iconElement,
.refrenamer-toggle {
margin: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.refrenamer-newname textarea {
height: 100%;
resize: none;
}
.refrenamer-keepcheck {
position: absolute;
${right}: 4px;
top: 50%;
transform: translateY(-50%);
margin: 0;
z-index: 1;
}
.refrenamer-keepcheck + .oo-ui-textInputWidget > textarea {
padding-${right}: 28px;
}
.refrenamer-newname .oo-ui-buttonElement-frameless {
position: absolute;
${right}: 0;
bottom: 0;
margin: 0;
}
.refrenamer .refrenamer-reapplybutton {
${right}: 24px;
}
.refrenamer-newname:not(:hover):not(:focus-within) > .oo-ui-buttonElement-frameless {
opacity: 0;
}
.refrenamer-newname .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button {
min-width: 24px;
min-height: 24px;
padding: 0;
}
.refrenamer-newname .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
background-size: 16px 16px;
left: 0;
right: 0;
margin: auto;
}
.refrenamer-propsbutton .oo-ui-menuOptionWidget {
font-size: 85%;
padding: 2px 8px;
}
.refrenamer-propsbutton .oo-ui-menuOptionWidget > .oo-ui-labelElement-label::before {
content: attr(data-refrenamer);
color: var(--color-subtle, #54595d);
float: ${right};
}
.refrenamer .refrenamer-addremove {
padding: 0;
position: relative;
width: 32px;
height: 32px;
}
.refrenamer-addremove .oo-ui-buttonElement-button {
height: 100%;
}
.refrenamer-othertable {
margin: 0;
}
.refrenamer-othertable .refrenamer-name {
max-width: 16em;
}
.refrenamer-othertable .refrenamer-ref {
position: relative;
}
.refrenamer-othertable .refrenamer-collapsible {
padding-right: 20px;
}
.refrenamer-toggle {
width: 100%;
background-position: center ${right} 4px;
background-repeat: no-repeat;
background-size: 12px 12px;
background-color: transparent;
border: none;
cursor: pointer;
z-index: 1;
}
.refrenamer-expanded > .refrenamer-toggle {
width: 20px;
${left}: auto;
}
.refrenamer-toggle:hover {
background-color: var(--background-color-button-quiet--hover, rgba(0,24,73,0.027));
}
.refrenamer-othertable .mw-reference-text {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.refrenamer-othertable .refrenamer-collapsible.refrenamer-expanded > .mw-reference-text {
-webkit-line-clamp: unset;
}`);
function RefRenamerDialog(config) {
RefRenamerDialog.parent.call(this, config);
this.$element.addClass('refrenamer');
}
OO.inheritClass(RefRenamerDialog, OO.ui.ProcessDialog);
RefRenamerDialog.static.name = 'refRenamerDialog';
RefRenamerDialog.static.title = 'RefRenamer';
RefRenamerDialog.static.size = 'large';
RefRenamerDialog.static.actions = [
{
flags: ['safe', 'close'],
modes: ['main', 'mainReset', 'other', 'otherReset']
},
{
action: 'continue',
flags: ['primary', 'progressive'],
label: getMsg('continue'),
modes: ['main', 'mainReset', 'other', 'otherReset']
},
{
action: 'addAll',
flags: ['progressive'],
label: getMsg('addAll'),
modes: ['other', 'otherReset']
},
{
action: 'resetSelection',
label: getMsg('resetSelection'),
modes: ['main', 'other']
}
];
RefRenamerDialog.prototype.initialize = function () {
RefRenamerDialog.parent.prototype.initialize.apply(this, arguments);
this.warning = new OO.ui.MessageWidget({
showClose: true,
type: 'warning'
}).toggle().connect(this, { close: 'updateSize' });
this.mainSelect = new OO.ui.MenuTagMultiselectWidget({
input: { autocomplete: false },
options: [
{ data: 'aulast', label: getMsg('lastName') },
{ data: 'aufirst', label: getMsg('firstName') },
{ data: 'au', label: getMsg('author') },
{ data: 'jtitle', label: getMsg('periodical') },
{ data: 'pub|inst', label: getMsg('publisher') },
{ data: 'atitle|title', label: getMsg('article') },
{ data: 'btitle', label: getMsg('book') },
{ data: 'domain|linkDomain', label: getMsg('domain') },
{ data: 'phrase', label: getMsg('firstPhrase') }
]
}).connect(this, { change: 'updateSize', reorder: 'updateSize' });
this.lowercaseCheck = new OO.ui.CheckboxInputWidget();
this.removeDiaCheck = new OO.ui.CheckboxInputWidget();
this.removePunctCheck = new OO.ui.CheckboxInputWidget();
this.replaceSpaceCheck = new OO.ui.CheckboxInputWidget();
this.replaceSpaceLayout = new OO.ui.FieldLayout(this.replaceSpaceCheck, {
align: 'inline',
label: getMsg('replaceSpace')
});
this.replaceSpaceInput = new OO.ui.TextInputWidget({
autocomplete: false
}).toggle().connect(this, { toggle: 'updateSize' });
this.replaceSpaceCheck.connect(this.replaceSpaceInput, { change: 'toggle' });
this.replaceSpaceLayout.$header.append(this.replaceSpaceInput.$element);
this.yearCheck = new OO.ui.CheckboxInputWidget();
this.yearLayout = new OO.ui.FieldLayout(this.yearCheck, {
align: 'inline',
label: getMsg('year')
});
this.yearFallbackCheck = new OO.ui.CheckboxInputWidget();
this.yearConvertCheck = new OO.ui.CheckboxInputWidget();
this.yearConvertLayout = new OO.ui.FieldLayout(this.yearConvertCheck, {
align: 'inline',
label: getMsg('yearConvert')
});
this.latinIncrementCheck = new OO.ui.CheckboxInputWidget();
this.yearSubLayout = new OO.ui.FieldsetLayout({
items: [
new OO.ui.FieldLayout(this.yearFallbackCheck, {
align: 'inline',
label: getMsg('yearFallback')
}),
this.yearConvertLayout,
new OO.ui.FieldLayout(this.latinIncrementCheck, {
align: 'inline',
label: getMsg('latinIncrement')
})
]
}).toggle().connect(this, { toggle: 'updateSize' });
this.yearCheck.connect(this.yearSubLayout, { change: 'toggle' });
this.yearLayout.$header.append(this.yearSubLayout.$element);
this.incrementDropdown = new OO.ui.DropdownWidget({
menu: {
items: [
new OO.ui.MenuOptionWidget({ data: [1, false] }),
new OO.ui.MenuOptionWidget({ data: [2, false] }),
new OO.ui.MenuOptionWidget({ data: [0, true] }),
new OO.ui.MenuOptionWidget({ data: [1, true] })
]
}
});
this.incrementDropdown.updateLabels = () => {
let example = getMsg('incrementExample');
let delimiter = this.delimitConditionalCheck.isSelected()
? ''
: this.delimiterInput.getValue();
this.incrementDropdown.getMenu().getItems().forEach(item => {
let [start, incrementAll] = item.getData();
let label = getMsg(
'incrementExamples',
example + (incrementAll ? delimiter + start : ''),
example + delimiter + (start + Number(incrementAll))
);
item.setLabel(label);
if (item.isSelected()) {
this.incrementDropdown.setLabel(label);
}
});
};
this.delimiterInput = new OO.ui.TextInputWidget({
autocomplete: false
}).connect(this.incrementDropdown, { change: 'updateLabels' });
this.delimitConditionalCheck = new OO.ui.CheckboxInputWidget()
.connect(this.incrementDropdown, { change: 'updateLabels' });
this.removeUnreusedCheck = new OO.ui.CheckboxInputWidget();
this.applyButton = new OO.ui.ButtonInputWidget({
flags: ['primary', 'progressive'],
label: getMsg('apply'),
type: 'submit'
}).connect(this, { click: 'applyConfig' });
this.resetButton = new OO.ui.ButtonWidget({
label: getMsg('reset')
}).connect(this, { click: 'setConfig' });
this.form = new OO.ui.FormLayout({
items: [
this.warning,
new OO.ui.FieldLayout(this.mainSelect, {
align: 'top',
label: getMsg('main')
}),
new OO.ui.FieldsetLayout({
classes: ['refrenamer-split'],
items: [
new OO.ui.FieldLayout(this.lowercaseCheck, {
align: 'inline',
label: getMsg('lowercase')
}),
new OO.ui.FieldLayout(this.removeDiaCheck, {
align: 'inline',
label: getMsg('removeDia')
}),
new OO.ui.FieldLayout(this.removePunctCheck, {
align: 'inline',
label: getMsg('removePunct')
}),
this.replaceSpaceLayout,
this.yearLayout,
new OO.ui.FieldLayout(this.incrementDropdown, {
align: 'top',
label: getMsg('increment')
}),
new OO.ui.FieldLayout(this.delimiterInput, {
align: 'top',
label: getMsg('delimiter')
}),
new OO.ui.FieldLayout(this.delimitConditionalCheck, {
align: 'inline',
label: getMsg('delimitConditional')
})
]
}),
new OO.ui.FieldLayout(this.removeUnreusedCheck, {
align: 'inline',
label: getMsg('removeUnreused')
}),
this.applyButton,
this.resetButton
]
});
this.$tbody = $('<tbody>');
this.$table = $('<table>').addClass('wikitable refrenamer-maintable').append(
$('<thead>').append(
$('<tr>').append(
$('<th>').text(getMsg('tableName')),
$('<th>').text(getMsg('tableRef')),
$('<th>').text(getMsg('tableNewName')),
$('<th>').text(getMsg('tableAddRemove'))
)
),
this.$tbody
);
this.$otherTbody = $('<tbody>');
this.index = new OO.ui.IndexLayout({
autoFocus: false
}).addTabPanels([
new OO.ui.TabPanelLayout('main', {
$content: $([this.form.$element[0], this.$table[0]]),
label: getMsg('tableCaption')
}),
new OO.ui.TabPanelLayout('other', {
$content: $('<table>').addClass('wikitable refrenamer-othertable').append(
$('<thead>').append(
$('<tr>').append(
$('<th>').text(getMsg('tableName')),
$('<th>').text(getMsg('tableRef')),
$('<th>').text(getMsg('tableAddRemove'))
)
),
this.$otherTbody
),
label: getMsg('otherTableCaption')
})
]).on('set', () => {
let mode = this.index.getCurrentTabPanelName();
if (this.refs.every(ref => ref.active === ref.isAuto)) {
mode += 'Reset';
}
this.actions.setMode(mode);
this.updateSize();
this.setupCollapsibles();
});
this.$body.append(this.index.$element);
this.defaults = {
main: ['aulast', 'aufirst', 'au', 'jtitle', 'pub|inst', 'phrase'],
lowercase: false,
removeDia: false,
removePunct: false,
replaceSpace: false,
year: true,
yearFallback: false,
yearConvert: true,
latinIncrement: true,
increment: 2,
incrementAll: false,
delimiter: '-',
delimitConditional: false,
removeUnreused: true
};
};
RefRenamerDialog.prototype.getConfig = function () {
let incrementData = this.incrementDropdown.getMenu()
.findSelectedItem().getData();
return {
main: this.mainSelect.getValue(),
lowercase: this.lowercaseCheck.isSelected(),
removeDia: this.removeDiaCheck.isSelected(),
removePunct: this.removePunctCheck.isSelected(),
replaceSpace: this.replaceSpaceCheck.isSelected() &&
this.replaceSpaceInput.getValue(),
year: this.yearCheck.isSelected(),
yearFallback: this.yearFallbackCheck.isSelected(),
yearConvert: this.yearConvertCheck.isSelected(),
latinIncrement: this.latinIncrementCheck.isSelected(),
increment: incrementData[0],
incrementAll: incrementData[1],
delimiter: this.delimiterInput.getValue(),
delimitConditional: this.delimitConditionalCheck.isSelected(),
removeUnreused: this.removeUnreusedCheck.isSelected()
};
};
RefRenamerDialog.prototype.setConfig = function (config) {
config = Object.assign({}, this.defaults, config);
this.mainSelect.setValue(config.main);
this.lowercaseCheck.setSelected(config.lowercase);
this.removeDiaCheck.setSelected(config.removeDia);
this.removePunctCheck.setSelected(config.removePunct);
let replaceSpace = typeof config.replaceSpace === 'string';
this.replaceSpaceCheck.setSelected(replaceSpace);
this.replaceSpaceInput
.setValue(replaceSpace ? config.replaceSpace : '-');
this.yearCheck.setSelected(config.year);
this.yearFallbackCheck.setSelected(config.yearFallback);
this.yearConvertCheck.setSelected(config.yearConvert);
this.latinIncrementCheck.setSelected(
// compatibility
typeof config.latinIncrement === 'number' || config.latinIncrement
);
let incrementMenu = this.incrementDropdown.getMenu();
let incrementItem = incrementMenu
.findItemFromData([config.increment, config.incrementAll]);
if (incrementItem) {
incrementMenu.selectItem(incrementItem);
} else {
incrementMenu.selectItemByData([2, false]);
}
this.delimiterInput.setValue(config.delimiter);
this.delimitConditionalCheck.setSelected(config.delimitConditional);
this.removeUnreusedCheck.setSelected(config.removeUnreused);
};
RefRenamerDialog.prototype.applyConfig = function (refs, forceKeep) {
refs = (refs || this.refs).filter(ref => ref.active);
if (!refs.length) return;
let config = this.getConfig(), withYear = new Set();
let stack = config.main.flatMap(k => k.split('|'));
let names = refs.map(ref => {
if (!forceKeep && ref.keepCheck) {
ref.keepCheck.setSelected(!config.removeUnreused);
if (config.removeUnreused) return;
}
let exports = {
name: ref.name,
props: ref.props,
getElement: () => ref.$ref.clone()[0]
};
mw.hook('refrenamer.rename').fire(exports);
if (typeof exports.newName === 'string') {
return exports.newName;
}
let s;
stack.some(k => (s = ref.props[k]));
if (!s) return;
if (config.lowercase) {
s = s.toLowerCase();
}
if (config.removeDia) {
s = s.normalize('NFD').replace(/\p{Mn}/gu, '');
}
if (config.removePunct) {
s = s.replace(/\p{P}/gu, '');
}
if (typeof config.replaceSpace === 'string') {
s = s.replace(/\s+/g, config.replaceSpace);
}
let year = config.year && (
config.yearConvert && ref.props.yearAscii ||
ref.props.year ||
config.yearFallback && (
config.yearConvert && ref.props.textYearAscii ||
ref.props.textYear
)
);
if (year) {
let delimiter = config.delimitConditional && /\P{Nd}$/u.test(s)
? ''
: config.delimiter;
s += delimiter + year;
withYear.add(ref);
}
return s;
});
let comps = names.map(s => s && normalize(s));
let hardComps = new Set();
this.refs.forEach(ref => {
if (refs.includes(ref)) return;
hardComps.add(
ref.active && normalize(ref.input.getValue()) ||
ref.normalized
);
});
refs.forEach((ref, i) => {
let s = names[i];
if (!s) {
ref.input.setValue('');
return;
}
let normalized = comps[i];
let useLatin = config.latinIncrement && withYear.has(ref);
let needsIncrement = hardComps.has(normalized) ||
comps.indexOf(normalized) !== i ||
(useLatin || config.incrementAll) &&
comps.slice(i + 1).includes(normalized);
if (needsIncrement) {
let unsuffixed = s;
let delimiter = useLatin || (
config.delimitConditional && /\P{Nd}$/u.test(s)
) ? '' : config.delimiter;
let increment = useLatin ? 0 : config.increment;
do {
s = unsuffixed + delimiter +
(useLatin ? toLatin(increment) : increment);
normalized = normalize(s);
increment++;
} while (hardComps.has(normalized) || comps.includes(normalized));
comps.push(normalized);
}
ref.input.setValue(s);
});
};
RefRenamerDialog.prototype.setActive = function (refs, active) {
refs.forEach(ref => {
ref.setActive(active === undefined ? ref.isAuto : active);
});
this.applyConfig(refs);
let activeCount = this.refs.filter(ref => ref.active).length;
let inactiveCount = this.refs.length - activeCount;
let tabs = this.index.getTabs().getItems();
tabs[0].$label.attr('data-refrenamer', activeCount);
tabs[1].setDisabled(!inactiveCount)
.$label.attr('data-refrenamer', inactiveCount);
this.$table.toggleClass('refrenamer-hidden', !activeCount);
if (!activeCount) {
this.index.setTabPanel('other');
} else if (!inactiveCount) {
this.index.setTabPanel('main');
} else {
this.index.emit('set');
}
};
RefRenamerDialog.prototype.toggleActive = function (ref) {
this.setActive([ref], !ref.active);
};
RefRenamerDialog.prototype.getSetupProcess = function (data) {
Object.assign(this, data);
let warnings = [];
if (this.templates.size) {
warnings.push(
document.createTextNode(getMsg('templatesWarn')),
$('<ul>').append(
[...this.templates].map(s => $('<li>').append(
'{{',
$('<a>').attr({
href: mw.Title.newFromText('Template:' + s).getUrl(),
target: '_blank'
}).text(s),
'}}'
))
)[0]
);
}
if (this.invalid.size) {
warnings.push(
document.createTextNode(getMsg('invalidWarn')),
$('<ul>').append(
[...this.invalid].map(s => $('<li>').text(s))
)[0]
);
}
this.warning.setLabel(warnings.length ? $(warnings) : '')
.toggle(warnings.length);
this.setConfig(mw.storage.getObject('refrenamer'));
this.$tbody.empty();
this.$otherTbody.empty();
this.refs.forEach(ref => {
ref.initProps();
ref.initRows();
});
this.setActive(this.refs);
let hasNonAsciiYear = this.refs.some(ref => (
ref.props.yearAscii || ref.props.textYearAscii
));
this.yearConvertLayout.toggle(hasNonAsciiYear);
this.index.getCurrentTabPanel().$element.scrollTop(0);
return RefRenamerDialog.super.prototype.getSetupProcess.call(this, data).next(function () {
this.index.emit('set');
}, this);
};
RefRenamerDialog.prototype.setupCollapsibles = mw.util.debounce(function () {
if (this.index.getCurrentTabPanelName() !== 'other') return;
let now = performance.now();
let refs = this.refs.filter(ref => !ref.active && ref.$toggle);
let width = refs[0].refCell.clientWidth;
refs.reverse().forEach(ref => {
ref.setupCollapsible(width);
});
console.log(performance.now() - now);
this.updateSize();
}, 200);
RefRenamerDialog.prototype.getActionProcess = function (action) {
if (action === 'addAll') {
this.setActive(this.refs, true);
} else if (action === 'resetSelection') {
this.setActive(this.refs);
}
return RefRenamerDialog.super.prototype.getActionProcess.call(this, action).next(function () {
if (action !== 'continue') return;
let subs = {}, integers = [], dupes = [];
let comps = this.refs.filter(ref => !ref.active).map(ref => ref.normalized);
let hasNonVe;
this.refs.forEach(ref => {
if (!ref.active) return;
if (ref.keepCheck && !ref.keepCheck.isSelected()) {
subs[ref.normalized] = null;
hasNonVe = hasNonVe || !ref.isVe;
return;
}
let newName = ref.input.getValue().replace(/^[\s_]+|[\s_]+$/g, '');
let normalized = normalize(newName);
if (!normalized || newName === ref.name && ref.names.size === 1) {
comps.push(ref.normalized);
} else if (!/\D/.test(normalized)) {
integers.push(newName);
} else if (comps.includes(normalized)) {
dupes.push(newName);
} else {
subs[ref.normalized] = newName;
hasNonVe = hasNonVe || !ref.isVe;
comps.push(normalized);
}
});
if (integers.length || dupes.length) {
return new OO.ui.Error($([
[integers, 'numericError'],
[dupes, 'duplicatesError']
].flatMap(([names, msgKey]) => names.length ? [
document.createTextNode(getMsg(msgKey)),
$('<ul>').append(names.map(n => $('<li>').text(n)))[0]
] : [])), { recoverable: false });
}
if (!Object.keys(subs).length) {
return new OO.ui.Error(
getMsg('noChangesError'),
{ recoverable: false }
);
}
this.close();
let newText = this.wikitext.replace(
/<(ref)\s+(name|follow)\s*=\s*(?:"\s*([^\n"]+?)\s*"?|'\s*([^\n']+?)\s*'?|([^\s>]+?))((?:\s*details\s*=\s*(?:"\s*[^\n"]+?\s*"?|'\s*[^\n']+?\s*'?|[^\s>]+?))?\s*\/?)>/gi,
(s, tag, attr, name1, name2, name3, slash) => {
let normalized = normalize(name1 || name2 || name3);
return Object.hasOwn(subs, normalized)
? subs[normalized]
? `<${tag} ${attr}="${subs[normalized]}"${slash}>`
: `<${tag}>`
: s;
}
);
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
let summary = hasNonVe
? getMsg('genericSummary', iw + 'User:Nardog/RefRenamer')
: getMsg(
'summary',
iw + 'Wikipedia:VisualEditor/Named references',
iw + 'User:Nardog/RefRenamer'
);
if (this.isEdit) {
$('#wpTextbox1').textSelection('setContents', newText);
if (document.documentElement.classList.contains('ve-active')) {
ve.init.target.once('showChanges', () => {
ve.init.target.saveDialog.reviewModeButtonSelect.selectItemByData('source');
ve.init.target.saveDialog.setEditSummary(summary);
});
ve.init.target.showSaveDialog('review');
} else {
let $summary = $('#wpSummary');
let oldSummary = $summary.textSelection('getContents').trim();
if (oldSummary) {
summary = /^\/\*.+\*\/$/.test(oldSummary)
? oldSummary + ' ' + summary
: oldSummary + getMsg('summarySeparator') + summary;
}
$summary.textSelection('setContents', summary);
$('#wpDiff').trigger('click');
}
} else {
this.$form.find('#wpTextbox1').val(newText).end()
.find('#wpSummary').val(summary).end()
.find('#wpMinoredit').prop('checked', true).end()
.appendTo(document.body)
.find('#wpDiff').trigger('click').end()
.remove();
notify('opening');
}
let customized = Object.entries(this.getConfig())
.filter(([k, v]) => String(v) !== String(this.defaults[k]));
if (customized.length) {
mw.storage.setObject('refrenamer', Object.fromEntries(customized), 7776000);
} else {
mw.storage.remove('refrenamer');
}
}, this);
};
RefRenamerDialog.prototype.hideErrors = function () {
RefRenamerDialog.super.prototype.hideErrors.call(this);
this.actions.setAbilities({ continue: true });
};
RefRenamerDialog.prototype.getBodyHeight = function () {
return this.index.$menu[0].scrollHeight +
this.index.getCurrentTabPanel().$element[0].scrollHeight;
};
dialog = new RefRenamerDialog();
window.refRenamerDialog = dialog;
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
};
let normalize = s => s.replace(/[\s_]+/g, '_').replace(/^_+|_+$/g, '');
let toAscii = s => s.replace(/\D/g, c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
});
let toLatin = n => {
let s = '';
do {
s = String.fromCharCode(97 + (n % 26)) + s;
n = Math.floor(n / 26) - 1;
} while (n >= 0);
return s;
};
let onToggle = function () {
let expanded = this.parentElement.classList.toggle('refrenamer-expanded');
let newAction = expanded ? 'collapse' : 'expand';
this.className = 'refrenamer-toggle oo-ui-icon-' + newAction;
this.title = getMsg(newAction);
dialog.updateSize();
};
window.refRenamer();
})();
i9752tj55lovypi4t3e8hkb1a739riy
783525
783480
2026-11-08T16:07:24Z
Nardog
40946
783525
javascript
text/javascript
/* globals ve */
window.refRenamerDialog?.close();
$('#mw-notification-area').empty();
$('head > style').filter(function () {
return this.textContent.includes('.refrenamer');
}).remove();
(() => {
let messages = Object.assign({
loadingSource: 'Loading the source...',
loadingHtml: 'Loading HTML...',
parsing: 'Parsing wikitext...',
opening: 'Opening the diff...',
continue: 'Continue',
main: 'Main fallback stack:',
lastName: 'Last name',
firstName: 'First name',
author: 'Author',
periodical: 'Periodical/website',
publisher: 'Publisher',
article: 'Article',
book: 'Book',
domain: 'Domain',
firstPhrase: 'First phrase',
lowercase: 'Lowercase',
removeDia: 'Remove diacritics',
removePunct: 'Remove punctuation',
replaceSpace: 'Replace space with:',
year: 'Year',
yearFallback: 'Fall back on any 4-digit number',
yearConvert: 'Convert to ASCII',
latinIncrement: 'Append Latin letters on collision',
increment: 'Collision resolution:',
incrementExample: 'Example',
incrementExamples: '$1, $2...',
delimiter: 'Delimiter:',
delimitConditional: 'Insert delimiters only after numerals',
removeUnreused: 'Remove unreused names',
apply: 'Apply',
reset: 'Reset',
tableName: 'Name',
tableCaption: 'References to rename',
tableRef: 'Reference',
tableNewName: 'New name',
tableAddRemove: '±',
reapplyTooltip: 'Reapply current options',
propsTooltip: 'View/insert properties',
keepTooltip: 'Uncheck to remove',
tableRemove: '(Remove)',
removeTooltip: 'Remove from references to rename',
otherTableCaption: 'Other named references',
notReused: '(not reused)',
expand: 'Expand',
collapse: 'Collapse',
addTooltip: 'Add to references to rename',
addAll: 'Add all',
resetSelection: 'Reset selection',
uneditableAlert: 'You do not appear to be able to edit this page.',
noNamesAlert: 'The source does not contain ref names to rename.',
noChangesError: 'No names have been modified.',
numericError: 'The following names are invalid as they consist only of numerals:',
duplicatesError: 'The following names are already used or input more than once:',
templatesWarn: 'Ref names in the following templates will not be replaced:',
invalidWarn: 'The following names have been ignored because it could not be determined which references correspond to them:',
summary: 'Replaced [[$1|VE ref names]] using [[$2|RefRenamer]]',
genericSummary: 'Renamed references using [[$1|RefRenamer]]',
summarySeparator: '; '
}, window.refrenamerMessages);
let getMsg = (key, ...args) => (
Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key
);
let notif;
let notify = async key => {
notif = await mw.notify(getMsg(key), {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
};
let dialog;
class Ref {
constructor(name, normalized) {
this.name = name;
this.names = new Set().add(name);
this.normalized = normalized;
this.isVe = /^(?::|(?:Book|Journal|News|Reference|Website)-)\d+$/.test(name);
this.isAuto = this.isVe || /^(?:auto(?:generated)?\d*|Reference[A-Z]+)$/.test(name);
}
initProps() {
this.props = {};
let coinsSpan = this.$ref[0].querySelector('.Z3988');
if (coinsSpan) {
new URLSearchParams(coinsSpan.title).forEach((v, k) => {
if (k.startsWith('rft.')) {
this.props[k.slice(4)] = v;
} else if (k === 'rft_id') {
if (/^https?:/.test(v)) {
if (this.props.domain) return;
try {
let url = new URL(v);
this.props.domain = url.hostname;
} catch {}
} else {
let match = v.match(/^info:([^/]+)\/(.+)$/);
if (match) {
this.props[match[1]] = match[2];
}
}
}
});
}
let text = this.$ref.text();
if (this.props.date) {
let numbers = this.props.date.match(/\p{Nd}+/gu);
if (numbers) {
let converted = numbers.map(n => toAscii(n));
let year = String(Math.max(...converted));
let original = numbers[converted.indexOf(year)];
this.props.year = original;
if (original !== year) {
this.props.yearAscii = year;
}
}
} else {
let match = text.match(/(?:^|\P{Nd})(\p{Nd}{4})(?!\p{Nd})/u);
if (match) {
this.props.textYear = match[1];
let ascii = toAscii(match[1]);
if (ascii !== match[1]) {
this.props.textYearAscii = ascii;
}
}
}
let link = this.$ref[0].querySelector('a.external');
if (link && link.hostname !== this.props.domain) {
this.props.linkDomain = link.hostname;
}
let match = text.match(/[^\s\p{P}].*?(?=\s*(?:\p{P}|$))/u);
if (match) {
this.props.phrase = match[0];
}
Object.freeze(this.props);
}
initRows() {
let rowClass = !this.reused && 'refrenamer-unreused';
this.$row = $('<tr>').addClass(rowClass).appendTo(dialog.$tbody);
this.$otherRow = $('<tr>').addClass(rowClass).appendTo(dialog.$otherTbody);
this.nameCell = $('<td>').addClass('refrenamer-name')
.append($('<span>').text(this.name))[0];
this.refCell = $('<td>').addClass('refrenamer-ref mw-parser-output')
.append(this.$ref)[0];
this.moveButton = new OO.ui.ButtonWidget({
framed: false,
invisibleLabel: true
}).connect(dialog, { click: ['toggleActive', this] });
this.moveCell = $('<td>').addClass('refrenamer-addremove')
.append(this.moveButton.$element)[0];
}
setActive(active) {
if (active === this.active) return;
this.active = active;
this[active ? '$otherRow' : '$row'].addClass('refrenamer-hidden');
let tooltip = getMsg(active ? 'removeTooltip' : 'addTooltip');
this.moveButton
.setFlags({ destructive: active, progressive: !active })
.setIcon(active ? 'subtract' : 'add')
.setLabel(tooltip).setTitle(tooltip);
this[active ? 'initInput' : 'initToggle']();
this[active ? '$row' : '$otherRow']
.prepend(this.nameCell, this.refCell)
.append(this.moveCell)
.removeClass('refrenamer-hidden');
}
initInput() {
if (this.input) return;
this.input = new OO.ui.MultilineTextInputWidget({
allowLinebreaks: false,
placeholder: this.name,
spellcheck: false
}).connect(dialog, { enter: ['executeAction', 'continue'] });
this.reapplyButton = new OO.ui.ButtonWidget({
classes: ['refrenamer-reapplybutton'],
framed: false,
icon: 'undo',
invisibleLabel: true,
label: getMsg('reapplyTooltip')
}).connect(dialog, {
click: ['applyConfig', [this], true]
}).connect(this.input, { click: 'focus' });
this.propsButton = new OO.ui.ButtonMenuSelectWidget({
classes: ['refrenamer-propsbutton'],
clearOnSelect: true,
framed: false,
icon: 'downTriangle',
invisibleLabel: true,
label: getMsg('propsTooltip'),
menu: {
$floatableContainer: this.input.$element,
horizontalPosition: 'end',
width: '16em'
}
});
this.propsButton.getMenu().addItems(
Object.entries(this.props).map(([k, v]) => {
let option = new OO.ui.MenuOptionWidget({
label: v,
title: v
});
option.$label.attr('data-refrenamer', k);
return option;
})
).connect(this, { choose: 'onPropChoose' });
if (!this.reused) {
this.keepCheck = new OO.ui.CheckboxInputWidget({
classes: ['refrenamer-keepcheck'],
invisibleLabel: true,
label: getMsg('keepTooltip')
}).connect(this, { change: 'onKeepChange' });
}
$('<td>').addClass('refrenamer-newname').append(
this.keepCheck?.$element,
this.input.$element,
this.reapplyButton.$element,
this.propsButton.$element
).appendTo(this.$row);
}
initToggle() {
if (this.$toggle) return;
this.$toggle = $('<button>').attr({
class: 'refrenamer-toggle oo-ui-icon-expand',
title: getMsg('expand')
}).on('click', onToggle).prependTo(this.refCell);
}
onPropChoose(item) {
this.input.insertContent(item.getTitle());
}
onKeepChange(selected) {
this.$row.toggleClass('refrenamer-kept', selected);
if (selected && !this.input.getValue()) {
dialog.applyConfig([this], true);
}
}
setupCollapsible(width) {
if (this.collapsible && Math.abs(width - this.refWidth) < 10) return;
this.refCell.classList.remove('refrenamer-collapsible');
this.collapsible = this.$ref[0].scrollHeight > this.$ref[0].clientHeight;
this.refCell.classList.toggle('refrenamer-collapsible', this.collapsible);
this.refWidth = width;
}
}
window.refRenamer = async () => {
let encodedPn = encodeURIComponent(mw.config.get('wgPageName'));
let headers = {
// 'Api-User-Agent': 'RefRenamer (https://en.wikipedia.org/wiki/User:Nardog/RefRenamer)'
};
let dependencies = [
'mediawiki.api', 'mediawiki.storage', 'mediawiki.Title',
'oojs-ui-windows', 'oojs-ui-widgets', 'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-editing-core'
];
let data = {
isEdit: !!document.getElementById('wpTextbox1') &&
!$('input[name="wpSection"]').val(),
refs: [],
templates: new Set(),
invalid: new Set()
};
try {
if (data.isEdit) {
dependencies.push('jquery.textSelection');
} else {
notify('loadingSource');
let response = await $.ajax(
mw.util.getUrl(null, { action: 'edit', venoscript: 1 }),
{ headers }
);
data.$form = $($($.parseHTML(response)).find('#editform').remove()[0]);
if (!data.$form.length) {
throw Error(getMsg('uneditableAlert'));
}
data.wikitext = data.$form.find('#wpTextbox1').val();
}
await mw.loader.using(dependencies);
if (data.isEdit) {
data.wikitext = $('#wpTextbox1').textSelection('getContents');
}
let wikitext = data.wikitext.replace(/<!--[^]*?-->/g, '');
for (let match of wikitext.matchAll(
/<ref\s+(?:name|follow)\s*=\s*(?:"\s*([^\n"]+?)\s*"?|'\s*([^\n']+?)\s*'?|([^\s>]+?))(?:\s*details\s*=\s*(?:"\s*[^\n"]+?\s*"?|'\s*[^\n']+?\s*'?|[^\s>]+?))?\s*\/?>/gi
)) {
let name = match[1] || match[2] || match[3];
let normalized = normalize(name);
let ref = data.refs.find(r => r.normalized === normalized);
if (ref) {
ref.reused = true;
ref.names.add(name);
} else {
data.refs.push(new Ref(name, normalized));
}
}
if (!data.refs.length) {
throw Error(getMsg('noNamesAlert'));
}
let response;
if (data.isEdit) {
notify('parsing');
response = await new mw.Rest().post(
'/v1/transform/wikitext/to/html/' + encodedPn,
{ wikitext: data.wikitext, body_only: true },
headers
);
} else {
notify('loadingHtml');
response = await new mw.Rest()
.get(`/v1/page/${encodedPn}/html`, null, headers);
}
let numbers = new Set();
let $page = $($.parseHTML(response));
$page.find(
'.mw-references:not([data-mw-group]) .mw-reference-text'
).each(function () {
let match = this.id.match(/^mw-reference-text-cite_note-(.+)-(\d+)$/);
if (!match) return;
let ref = data.refs.find(r => r.normalized === match[1]);
if (!ref) return;
if (ref.$ref) {
data.invalid.add(ref.name);
ref.invalid = true;
return;
}
ref.$ref = $(this);
ref.reused = ref.reused || (
ref.$ref.prev('span[rel="mw:referencedBy"]').children().length > 1
);
ref.$ref.remove().find('[id], [about]').addBack().removeAttr('id about');
ref.$ref.find('a').attr('target', '_blank')
.filter('[href^="./"]').attr('href', (_, href) => (
mw.format(mw.config.get('wgArticlePath'), href.slice(2))
));
numbers.add(match[2]);
});
let tempRe = new RegExp(`^\\./${
mw.util.escapeRegExp(mw.config.get('wgFormattedNamespaces')[10])
}:`);
$page.find(
'.mw-ref[typeof~="mw:Transclusion"], [typeof~="mw:Transclusion"] .mw-ref'
).filter(function () {
if (!/^\[\d+\]$/.test(this.textContent)) return;
let match = this.id.match(/_(\d+)-\d+$/);
return match && numbers.has(match[1]);
}).closest('[typeof~="mw:Transclusion"]').each(function () {
try {
data.templates.add(
JSON.parse(this.dataset.mw).parts[0].template.target.href
.replace(tempRe, '')
);
} catch {}
});
data.refs = data.refs.filter(ref => ref.$ref && !ref.invalid);
if (!data.refs.length) {
throw Error(getMsg('noNamesAlert'));
}
let collator;
try {
collator = Intl.Collator(mw.config.get('wgContentLanguage') + '-u-kn-true');
} catch {
collator = Intl.Collator('en-u-kn-true');
}
data.refs.sort((a, b) => collator.compare(a.name, b.name));
if (!dialog) {
initDialog();
}
dialog.open(data);
} catch (error) {
OO.ui.alert(error.message || error.responseText || error);
} finally {
if (notif) {
notif.close();
notif = null;
}
}
};
let initDialog = () => {
let rtl = document.dir === 'rtl';
let left = rtl ? 'right' : 'left';
let right = rtl ? 'left' : 'right';
mw.loader.addStyleTag(`.refrenamer .oo-ui-tabOptionWidget > .oo-ui-labelElement-label::after {
content: " (" attr(data-refrenamer) ")";
}
.refrenamer-split {
columns: 2;
}
.refrenamer-split .oo-ui-fieldLayout {
break-inside: avoid;
}
.refrenamer-split,
.refrenamer .oo-ui-layout.oo-ui-labelElement:nth-child(n+2),
.refrenamer .oo-ui-fieldLayout-align-inline .oo-ui-labelElement-label + * {
margin: 4px 0;
}
.refrenamer .oo-ui-textInputWidget > .oo-ui-inputWidget-input {
font-family: monospace,monospace;
}
.refrenamer .wikitable {
margin-bottom: 0;
overflow-wrap: break-word;
width: 100%;
}
.refrenamer-maintable .refrenamer-name,
.refrenamer .mw-reference-text,
.refrenamer-othertable .refrenamer-name::after {
font-size: 90%;
}
.refrenamer-unreused > .refrenamer-name::after {
content: "${getMsg('notReused')}";
display: inline-block;
}
.refrenamer-unreused > .refrenamer-name > span::after {
content: " ";
}
.refrenamer-hidden,
.refrenamer-unreused:not(.refrenamer-kept) > .refrenamer-newname > :not(.refrenamer-keepcheck),
.refrenamer-maintable .refrenamer-toggle,
:not(.refrenamer-collapsible) > .refrenamer-toggle {
display: none;
}
.refrenamer-name,
.refrenamer-ref {
vertical-align: top;
}
.refrenamer-maintable .refrenamer-name {
max-width: 6em;
}
.refrenamer-ref {
line-height: normal;
}
.refrenamer-newname {
height: 3em;
position: relative;
width: 12em;
}
.refrenamer-unreused:not(.refrenamer-kept) > .refrenamer-newname::after {
content: "${getMsg('tableRemove')}";
}
.refrenamer-newname > .oo-ui-textInputWidget,
.refrenamer-addremove > .oo-ui-buttonElement-frameless.oo-ui-iconElement,
.refrenamer-toggle {
margin: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.refrenamer-newname textarea {
height: 100%;
resize: none;
}
.refrenamer-keepcheck {
position: absolute;
${right}: 4px;
top: 50%;
transform: translateY(-50%);
margin: 0;
z-index: 1;
}
.refrenamer-keepcheck + .oo-ui-textInputWidget > textarea {
padding-${right}: 28px;
}
.refrenamer-newname .oo-ui-buttonElement-frameless {
position: absolute;
${right}: 0;
bottom: 0;
margin: 0;
}
.refrenamer .refrenamer-reapplybutton {
${right}: 24px;
}
.refrenamer-newname:not(:hover):not(:focus-within) > .oo-ui-buttonElement-frameless {
opacity: 0;
}
.refrenamer-newname .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button {
min-width: 24px;
min-height: 24px;
padding: 0;
}
.refrenamer-newname .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
background-size: 16px 16px;
left: 0;
right: 0;
margin: auto;
}
.refrenamer-propsbutton .oo-ui-menuOptionWidget {
font-size: 85%;
padding: 2px 8px;
}
.refrenamer-propsbutton .oo-ui-menuOptionWidget > .oo-ui-labelElement-label::before {
content: attr(data-refrenamer);
color: var(--color-subtle, #54595d);
float: ${right};
}
.refrenamer .refrenamer-addremove {
padding: 0;
position: relative;
width: 32px;
height: 32px;
}
.refrenamer-addremove .oo-ui-buttonElement-button {
height: 100%;
}
.refrenamer-othertable {
margin: 0;
}
.refrenamer-othertable .refrenamer-name {
max-width: 16em;
}
.refrenamer-othertable .refrenamer-ref {
position: relative;
}
.refrenamer-othertable .refrenamer-collapsible {
padding-right: 20px;
}
.refrenamer-toggle {
width: 100%;
background-position: center ${right} 4px;
background-repeat: no-repeat;
background-size: 12px 12px;
background-color: transparent;
border: none;
cursor: pointer;
z-index: 1;
}
.refrenamer-expanded > .refrenamer-toggle {
width: 20px;
${left}: auto;
}
.refrenamer-toggle:hover {
background-color: var(--background-color-button-quiet--hover, rgba(0,24,73,0.027));
}
.refrenamer-othertable .mw-reference-text {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.refrenamer-othertable .refrenamer-collapsible.refrenamer-expanded > .mw-reference-text {
-webkit-line-clamp: unset;
}`);
function RefRenamerDialog(config) {
RefRenamerDialog.parent.call(this, config);
this.$element.addClass('refrenamer');
}
OO.inheritClass(RefRenamerDialog, OO.ui.ProcessDialog);
RefRenamerDialog.static.name = 'refRenamerDialog';
RefRenamerDialog.static.title = 'RefRenamer';
RefRenamerDialog.static.size = 'large';
RefRenamerDialog.static.actions = [
{
flags: ['safe', 'close'],
modes: ['main', 'mainReset', 'other', 'otherReset']
},
{
action: 'continue',
flags: ['primary', 'progressive'],
label: getMsg('continue'),
modes: ['main', 'mainReset', 'other', 'otherReset']
},
{
action: 'addAll',
flags: ['progressive'],
label: getMsg('addAll'),
modes: ['other', 'otherReset']
},
{
action: 'resetSelection',
label: getMsg('resetSelection'),
modes: ['main', 'other']
}
];
RefRenamerDialog.prototype.initialize = function () {
RefRenamerDialog.parent.prototype.initialize.apply(this, arguments);
this.warning = new OO.ui.MessageWidget({
showClose: true,
type: 'warning'
}).toggle().connect(this, { close: 'updateSize' });
this.mainSelect = new OO.ui.MenuTagMultiselectWidget({
input: { autocomplete: false },
options: [
{ data: 'aulast', label: getMsg('lastName') },
{ data: 'aufirst', label: getMsg('firstName') },
{ data: 'au', label: getMsg('author') },
{ data: 'jtitle', label: getMsg('periodical') },
{ data: 'pub|inst', label: getMsg('publisher') },
{ data: 'atitle|title', label: getMsg('article') },
{ data: 'btitle', label: getMsg('book') },
{ data: 'domain|linkDomain', label: getMsg('domain') },
{ data: 'phrase', label: getMsg('firstPhrase') }
]
}).connect(this, { change: 'updateSize', reorder: 'updateSize' });
this.lowercaseCheck = new OO.ui.CheckboxInputWidget();
this.removeDiaCheck = new OO.ui.CheckboxInputWidget();
this.removePunctCheck = new OO.ui.CheckboxInputWidget();
this.replaceSpaceCheck = new OO.ui.CheckboxInputWidget();
this.replaceSpaceLayout = new OO.ui.FieldLayout(this.replaceSpaceCheck, {
align: 'inline',
label: getMsg('replaceSpace')
});
this.replaceSpaceInput = new OO.ui.TextInputWidget({
autocomplete: false
}).toggle().connect(this, { toggle: 'updateSize' });
this.replaceSpaceCheck.connect(this.replaceSpaceInput, { change: 'toggle' });
this.replaceSpaceLayout.$header.append(this.replaceSpaceInput.$element);
this.yearCheck = new OO.ui.CheckboxInputWidget();
this.yearLayout = new OO.ui.FieldLayout(this.yearCheck, {
align: 'inline',
label: getMsg('year')
});
this.yearFallbackCheck = new OO.ui.CheckboxInputWidget();
this.yearConvertCheck = new OO.ui.CheckboxInputWidget();
this.yearConvertLayout = new OO.ui.FieldLayout(this.yearConvertCheck, {
align: 'inline',
label: getMsg('yearConvert')
});
this.latinIncrementCheck = new OO.ui.CheckboxInputWidget();
this.yearSubLayout = new OO.ui.FieldsetLayout({
items: [
new OO.ui.FieldLayout(this.yearFallbackCheck, {
align: 'inline',
label: getMsg('yearFallback')
}),
this.yearConvertLayout,
new OO.ui.FieldLayout(this.latinIncrementCheck, {
align: 'inline',
label: getMsg('latinIncrement')
})
]
}).toggle().connect(this, { toggle: 'updateSize' });
this.yearCheck.connect(this.yearSubLayout, { change: 'toggle' });
this.yearLayout.$header.append(this.yearSubLayout.$element);
this.incrementDropdown = new OO.ui.DropdownWidget({
menu: {
items: [
new OO.ui.MenuOptionWidget({ data: [1, false] }),
new OO.ui.MenuOptionWidget({ data: [2, false] }),
new OO.ui.MenuOptionWidget({ data: [0, true] }),
new OO.ui.MenuOptionWidget({ data: [1, true] })
]
}
});
this.incrementDropdown.updateLabels = () => {
let example = getMsg('incrementExample');
let delimiter = this.delimitConditionalCheck.isSelected()
? ''
: this.delimiterInput.getValue();
this.incrementDropdown.getMenu().getItems().forEach(item => {
let [start, incrementAll] = item.getData();
let label = getMsg(
'incrementExamples',
example + (incrementAll ? delimiter + start : ''),
example + delimiter + (start + Number(incrementAll))
);
item.setLabel(label);
if (item.isSelected()) {
this.incrementDropdown.setLabel(label);
}
});
};
this.delimiterInput = new OO.ui.TextInputWidget({
autocomplete: false
}).connect(this.incrementDropdown, { change: 'updateLabels' });
this.delimitConditionalCheck = new OO.ui.CheckboxInputWidget()
.connect(this.incrementDropdown, { change: 'updateLabels' });
this.removeUnreusedCheck = new OO.ui.CheckboxInputWidget();
this.applyButton = new OO.ui.ButtonInputWidget({
flags: ['primary', 'progressive'],
label: getMsg('apply'),
type: 'submit'
}).connect(this, { click: 'applyConfig' });
this.resetButton = new OO.ui.ButtonWidget({
label: getMsg('reset')
}).connect(this, { click: 'setConfig' });
this.form = new OO.ui.FormLayout({
items: [
this.warning,
new OO.ui.FieldLayout(this.mainSelect, {
align: 'top',
label: getMsg('main')
}),
new OO.ui.FieldsetLayout({
classes: ['refrenamer-split'],
items: [
new OO.ui.FieldLayout(this.lowercaseCheck, {
align: 'inline',
label: getMsg('lowercase')
}),
new OO.ui.FieldLayout(this.removeDiaCheck, {
align: 'inline',
label: getMsg('removeDia')
}),
new OO.ui.FieldLayout(this.removePunctCheck, {
align: 'inline',
label: getMsg('removePunct')
}),
this.replaceSpaceLayout,
this.yearLayout,
new OO.ui.FieldLayout(this.incrementDropdown, {
align: 'top',
label: getMsg('increment')
}),
new OO.ui.FieldLayout(this.delimiterInput, {
align: 'top',
label: getMsg('delimiter')
}),
new OO.ui.FieldLayout(this.delimitConditionalCheck, {
align: 'inline',
label: getMsg('delimitConditional')
})
]
}),
new OO.ui.FieldLayout(this.removeUnreusedCheck, {
align: 'inline',
label: getMsg('removeUnreused')
}),
this.applyButton,
this.resetButton
]
});
this.$tbody = $('<tbody>');
this.$table = $('<table>').addClass('wikitable refrenamer-maintable').append(
$('<thead>').append(
$('<tr>').append(
$('<th>').text(getMsg('tableName')),
$('<th>').text(getMsg('tableRef')),
$('<th>').text(getMsg('tableNewName')),
$('<th>').text(getMsg('tableAddRemove'))
)
),
this.$tbody
);
this.$otherTbody = $('<tbody>');
this.index = new OO.ui.IndexLayout({
autoFocus: false
}).addTabPanels([
new OO.ui.TabPanelLayout('main', {
$content: $([this.form.$element[0], this.$table[0]]),
label: getMsg('tableCaption')
}),
new OO.ui.TabPanelLayout('other', {
$content: $('<table>').addClass('wikitable refrenamer-othertable').append(
$('<thead>').append(
$('<tr>').append(
$('<th>').text(getMsg('tableName')),
$('<th>').text(getMsg('tableRef')),
$('<th>').text(getMsg('tableAddRemove'))
)
),
this.$otherTbody
),
label: getMsg('otherTableCaption')
})
]).on('set', () => {
let mode = this.index.getCurrentTabPanelName();
if (this.refs.every(ref => ref.active === ref.isAuto)) {
mode += 'Reset';
}
this.actions.setMode(mode);
this.updateSize();
this.setupCollapsibles();
});
this.$body.append(this.index.$element);
this.defaults = {
main: ['aulast', 'aufirst', 'au', 'jtitle', 'pub|inst', 'phrase'],
lowercase: false,
removeDia: false,
removePunct: false,
replaceSpace: false,
year: true,
yearFallback: false,
yearConvert: true,
latinIncrement: true,
increment: 2,
incrementAll: false,
delimiter: '-',
delimitConditional: false,
removeUnreused: true
};
};
RefRenamerDialog.prototype.getConfig = function () {
let incrementData = this.incrementDropdown.getMenu()
.findSelectedItem().getData();
return {
main: this.mainSelect.getValue(),
lowercase: this.lowercaseCheck.isSelected(),
removeDia: this.removeDiaCheck.isSelected(),
removePunct: this.removePunctCheck.isSelected(),
replaceSpace: this.replaceSpaceCheck.isSelected() &&
this.replaceSpaceInput.getValue(),
year: this.yearCheck.isSelected(),
yearFallback: this.yearFallbackCheck.isSelected(),
yearConvert: this.yearConvertCheck.isSelected(),
latinIncrement: this.latinIncrementCheck.isSelected(),
increment: incrementData[0],
incrementAll: incrementData[1],
delimiter: this.delimiterInput.getValue(),
delimitConditional: this.delimitConditionalCheck.isSelected(),
removeUnreused: this.removeUnreusedCheck.isSelected()
};
};
RefRenamerDialog.prototype.setConfig = function (config) {
config = Object.assign({}, this.defaults, config);
this.mainSelect.setValue(config.main);
this.lowercaseCheck.setSelected(config.lowercase);
this.removeDiaCheck.setSelected(config.removeDia);
this.removePunctCheck.setSelected(config.removePunct);
let replaceSpace = typeof config.replaceSpace === 'string';
this.replaceSpaceCheck.setSelected(replaceSpace);
this.replaceSpaceInput
.setValue(replaceSpace ? config.replaceSpace : '-');
this.yearCheck.setSelected(config.year);
this.yearFallbackCheck.setSelected(config.yearFallback);
this.yearConvertCheck.setSelected(config.yearConvert);
this.latinIncrementCheck.setSelected(
// compatibility
typeof config.latinIncrement === 'number' || config.latinIncrement
);
let incrementMenu = this.incrementDropdown.getMenu();
let incrementItem = incrementMenu
.findItemFromData([config.increment, config.incrementAll]);
if (incrementItem) {
incrementMenu.selectItem(incrementItem);
} else {
incrementMenu.selectItemByData([2, false]);
}
this.delimiterInput.setValue(config.delimiter);
this.delimitConditionalCheck.setSelected(config.delimitConditional);
this.removeUnreusedCheck.setSelected(config.removeUnreused);
};
RefRenamerDialog.prototype.applyConfig = function (refs, forceKeep) {
refs = (refs || this.refs).filter(ref => ref.active);
if (!refs.length) return;
let config = this.getConfig(), withYear = new Set();
let stack = config.main.flatMap(k => k.split('|'));
let names = refs.map(ref => {
if (!forceKeep && ref.keepCheck && ref.isAuto) {
ref.keepCheck.setSelected(!config.removeUnreused && ref.isAuto);
if (config.removeUnreused) return;
}
let exports = {
name: ref.name,
props: ref.props,
getElement: () => ref.$ref.clone()[0]
};
mw.hook('refrenamer.rename').fire(exports);
if (typeof exports.newName === 'string') {
return exports.newName;
}
let s;
stack.some(k => (s = ref.props[k]));
if (!s) return;
if (config.lowercase) {
s = s.toLowerCase();
}
if (config.removeDia) {
s = s.normalize('NFD').replace(/\p{Mn}/gu, '');
}
if (config.removePunct) {
s = s.replace(/\p{P}/gu, '');
}
if (typeof config.replaceSpace === 'string') {
s = s.replace(/\s+/g, config.replaceSpace);
}
let year = config.year && (
config.yearConvert && ref.props.yearAscii ||
ref.props.year ||
config.yearFallback && (
config.yearConvert && ref.props.textYearAscii ||
ref.props.textYear
)
);
if (year) {
let delimiter = config.delimitConditional && /\P{Nd}$/u.test(s)
? ''
: config.delimiter;
s += delimiter + year;
withYear.add(ref);
}
return s;
});
let comps = names.map(s => s && normalize(s));
let hardComps = new Set();
this.refs.forEach(ref => {
if (refs.includes(ref)) return;
hardComps.add(
ref.active && normalize(ref.input.getValue()) ||
ref.normalized
);
});
refs.forEach((ref, i) => {
let s = names[i];
if (!s) {
ref.input.setValue('');
return;
}
let normalized = comps[i];
let useLatin = config.latinIncrement && withYear.has(ref);
let needsIncrement = hardComps.has(normalized) ||
comps.indexOf(normalized) !== i ||
(useLatin || config.incrementAll) &&
comps.slice(i + 1).includes(normalized);
if (needsIncrement) {
let unsuffixed = s;
let delimiter = useLatin || (
config.delimitConditional && /\P{Nd}$/u.test(s)
) ? '' : config.delimiter;
let increment = useLatin ? 0 : config.increment;
do {
s = unsuffixed + delimiter +
(useLatin ? toLatin(increment) : increment);
normalized = normalize(s);
increment++;
} while (hardComps.has(normalized) || comps.includes(normalized));
comps.push(normalized);
}
ref.input.setValue(s);
});
};
RefRenamerDialog.prototype.setActive = function (refs, active) {
refs.forEach(ref => {
ref.setActive(active === undefined ? ref.isAuto : active);
});
this.applyConfig(refs);
let activeCount = this.refs.filter(ref => ref.active).length;
let inactiveCount = this.refs.length - activeCount;
let tabs = this.index.getTabs().getItems();
tabs[0].$label.attr('data-refrenamer', activeCount);
tabs[1].setDisabled(!inactiveCount)
.$label.attr('data-refrenamer', inactiveCount);
this.$table.toggleClass('refrenamer-hidden', !activeCount);
if (!activeCount) {
this.index.setTabPanel('other');
} else if (!inactiveCount) {
this.index.setTabPanel('main');
} else {
this.index.emit('set');
}
};
RefRenamerDialog.prototype.toggleActive = function (ref) {
this.setActive([ref], !ref.active);
};
RefRenamerDialog.prototype.getSetupProcess = function (data) {
Object.assign(this, data);
let warnings = [];
if (this.templates.size) {
warnings.push(
document.createTextNode(getMsg('templatesWarn')),
$('<ul>').append(
[...this.templates].map(s => $('<li>').append(
'{{',
$('<a>').attr({
href: mw.Title.newFromText('Template:' + s).getUrl(),
target: '_blank'
}).text(s),
'}}'
))
)[0]
);
}
if (this.invalid.size) {
warnings.push(
document.createTextNode(getMsg('invalidWarn')),
$('<ul>').append(
[...this.invalid].map(s => $('<li>').text(s))
)[0]
);
}
this.warning.setLabel(warnings.length ? $(warnings) : '')
.toggle(warnings.length);
this.setConfig(mw.storage.getObject('refrenamer'));
this.$tbody.empty();
this.$otherTbody.empty();
this.refs.forEach(ref => {
ref.initProps();
ref.initRows();
});
this.setActive(this.refs);
let hasNonAsciiYear = this.refs.some(ref => (
ref.props.yearAscii || ref.props.textYearAscii
));
this.yearConvertLayout.toggle(hasNonAsciiYear);
this.index.getCurrentTabPanel().$element.scrollTop(0);
return RefRenamerDialog.super.prototype.getSetupProcess.call(this, data).next(function () {
this.index.emit('set');
}, this);
};
RefRenamerDialog.prototype.setupCollapsibles = mw.util.debounce(function () {
if (this.index.getCurrentTabPanelName() !== 'other') return;
let now = performance.now();
let refs = this.refs.filter(ref => !ref.active && ref.$toggle);
let width = refs[0].refCell.clientWidth;
refs.reverse().forEach(ref => {
ref.setupCollapsible(width);
});
console.log(performance.now() - now);
this.updateSize();
}, 200);
RefRenamerDialog.prototype.getActionProcess = function (action) {
if (action === 'addAll') {
this.setActive(this.refs, true);
} else if (action === 'resetSelection') {
this.setActive(this.refs);
}
return RefRenamerDialog.super.prototype.getActionProcess.call(this, action).next(function () {
if (action !== 'continue') return;
let subs = {}, integers = [], dupes = [];
let comps = this.refs.filter(ref => !ref.active).map(ref => ref.normalized);
let hasNonVe;
this.refs.forEach(ref => {
if (!ref.active) return;
if (ref.keepCheck && !ref.keepCheck.isSelected()) {
subs[ref.normalized] = null;
hasNonVe = hasNonVe || !ref.isVe;
return;
}
let newName = ref.input.getValue().replace(/^[\s_]+|[\s_]+$/g, '');
let normalized = normalize(newName);
if (!normalized || newName === ref.name && ref.names.size === 1) {
comps.push(ref.normalized);
} else if (!/\D/.test(normalized)) {
integers.push(newName);
} else if (comps.includes(normalized)) {
dupes.push(newName);
} else {
subs[ref.normalized] = newName;
hasNonVe = hasNonVe || !ref.isVe;
comps.push(normalized);
}
});
if (integers.length || dupes.length) {
return new OO.ui.Error($([
[integers, 'numericError'],
[dupes, 'duplicatesError']
].flatMap(([names, msgKey]) => names.length ? [
document.createTextNode(getMsg(msgKey)),
$('<ul>').append(names.map(n => $('<li>').text(n)))[0]
] : [])), { recoverable: false });
}
if (!Object.keys(subs).length) {
return new OO.ui.Error(
getMsg('noChangesError'),
{ recoverable: false }
);
}
this.close();
let newText = this.wikitext.replace(
/<(ref)\s+(name|follow)\s*=\s*(?:"\s*([^\n"]+?)\s*"?|'\s*([^\n']+?)\s*'?|([^\s>]+?))((?:\s*details\s*=\s*(?:"\s*[^\n"]+?\s*"?|'\s*[^\n']+?\s*'?|[^\s>]+?))?\s*\/?)>/gi,
(s, tag, attr, name1, name2, name3, slash) => {
let normalized = normalize(name1 || name2 || name3);
return Object.hasOwn(subs, normalized)
? subs[normalized]
? `<${tag} ${attr}="${subs[normalized]}"${slash}>`
: `<${tag}>`
: s;
}
);
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
let summary = hasNonVe
? getMsg('genericSummary', iw + 'User:Nardog/RefRenamer')
: getMsg(
'summary',
iw + 'Wikipedia:VisualEditor/Named references',
iw + 'User:Nardog/RefRenamer'
);
if (this.isEdit) {
$('#wpTextbox1').textSelection('setContents', newText);
if (document.documentElement.classList.contains('ve-active')) {
ve.init.target.once('showChanges', () => {
ve.init.target.saveDialog.reviewModeButtonSelect.selectItemByData('source');
ve.init.target.saveDialog.setEditSummary(summary);
});
ve.init.target.showSaveDialog('review');
} else {
let $summary = $('#wpSummary');
let oldSummary = $summary.textSelection('getContents').trim();
if (oldSummary) {
summary = /^\/\*.+\*\/$/.test(oldSummary)
? oldSummary + ' ' + summary
: oldSummary + getMsg('summarySeparator') + summary;
}
$summary.textSelection('setContents', summary);
$('#wpDiff').trigger('click');
}
} else {
this.$form.find('#wpTextbox1').val(newText).end()
.find('#wpSummary').val(summary).end()
.find('#wpMinoredit').prop('checked', true).end()
.appendTo(document.body)
.find('#wpDiff').trigger('click').end()
.remove();
notify('opening');
}
let customized = Object.entries(this.getConfig())
.filter(([k, v]) => String(v) !== String(this.defaults[k]));
if (customized.length) {
mw.storage.setObject('refrenamer', Object.fromEntries(customized), 7776000);
} else {
mw.storage.remove('refrenamer');
}
}, this);
};
RefRenamerDialog.prototype.hideErrors = function () {
RefRenamerDialog.super.prototype.hideErrors.call(this);
this.actions.setAbilities({ continue: true });
};
RefRenamerDialog.prototype.getBodyHeight = function () {
return this.index.$menu[0].scrollHeight +
this.index.getCurrentTabPanel().$element[0].scrollHeight;
};
dialog = new RefRenamerDialog();
window.refRenamerDialog = dialog;
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
};
let normalize = s => s.replace(/[\s_]+/g, '_').replace(/^_+|_+$/g, '');
let toAscii = s => s.replace(/\D/g, c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
});
let toLatin = n => {
let s = '';
do {
s = String.fromCharCode(97 + (n % 26)) + s;
n = Math.floor(n / 26) - 1;
} while (n >= 0);
return s;
};
let onToggle = function () {
let expanded = this.parentElement.classList.toggle('refrenamer-expanded');
let newAction = expanded ? 'collapse' : 'expand';
this.className = 'refrenamer-toggle oo-ui-icon-' + newAction;
this.title = getMsg(newAction);
dialog.updateSize();
};
window.refRenamer();
})();
a9nz0lgv6bl5kofna6nnkf7dg131193
783526
783525
2026-11-08T16:08:31Z
Nardog
40946
783526
javascript
text/javascript
/* globals ve */
window.refRenamerDialog?.close();
$('#mw-notification-area').empty();
$('head > style').filter(function () {
return this.textContent.includes('.refrenamer');
}).remove();
(() => {
let messages = Object.assign({
loadingSource: 'Loading the source...',
loadingHtml: 'Loading HTML...',
parsing: 'Parsing wikitext...',
opening: 'Opening the diff...',
continue: 'Continue',
main: 'Main fallback stack:',
lastName: 'Last name',
firstName: 'First name',
author: 'Author',
periodical: 'Periodical/website',
publisher: 'Publisher',
article: 'Article',
book: 'Book',
domain: 'Domain',
firstPhrase: 'First phrase',
lowercase: 'Lowercase',
removeDia: 'Remove diacritics',
removePunct: 'Remove punctuation',
replaceSpace: 'Replace space with:',
year: 'Year',
yearFallback: 'Fall back on any 4-digit number',
yearConvert: 'Convert to ASCII',
latinIncrement: 'Append Latin letters on collision',
increment: 'Collision resolution:',
incrementExample: 'Example',
incrementExamples: '$1, $2...',
delimiter: 'Delimiter:',
delimitConditional: 'Insert delimiters only after numerals',
removeUnreused: 'Remove unreused names',
apply: 'Apply',
reset: 'Reset',
tableName: 'Name',
tableCaption: 'References to rename',
tableRef: 'Reference',
tableNewName: 'New name',
tableAddRemove: '±',
reapplyTooltip: 'Reapply current options',
propsTooltip: 'View/insert properties',
keepTooltip: 'Uncheck to remove',
tableRemove: '(Remove)',
removeTooltip: 'Remove from references to rename',
otherTableCaption: 'Other named references',
notReused: '(not reused)',
expand: 'Expand',
collapse: 'Collapse',
addTooltip: 'Add to references to rename',
addAll: 'Add all',
resetSelection: 'Reset selection',
uneditableAlert: 'You do not appear to be able to edit this page.',
noNamesAlert: 'The source does not contain ref names to rename.',
noChangesError: 'No names have been modified.',
numericError: 'The following names are invalid as they consist only of numerals:',
duplicatesError: 'The following names are already used or input more than once:',
templatesWarn: 'Ref names in the following templates will not be replaced:',
invalidWarn: 'The following names have been ignored because it could not be determined which references correspond to them:',
summary: 'Replaced [[$1|VE ref names]] using [[$2|RefRenamer]]',
genericSummary: 'Renamed references using [[$1|RefRenamer]]',
summarySeparator: '; '
}, window.refrenamerMessages);
let getMsg = (key, ...args) => (
Object.hasOwn(messages, key) ? mw.format(messages[key], ...args) : key
);
let notif;
let notify = async key => {
notif = await mw.notify(getMsg(key), {
autoHideSeconds: 'long',
tag: 'refrenamer'
});
};
let dialog;
class Ref {
constructor(name, normalized) {
this.name = name;
this.names = new Set().add(name);
this.normalized = normalized;
this.isVe = /^(?::|(?:Book|Journal|News|Reference|Website)-)\d+$/.test(name);
this.isAuto = this.isVe || /^(?:auto(?:generated)?\d*|Reference[A-Z]+)$/.test(name);
}
initProps() {
this.props = {};
let coinsSpan = this.$ref[0].querySelector('.Z3988');
if (coinsSpan) {
new URLSearchParams(coinsSpan.title).forEach((v, k) => {
if (k.startsWith('rft.')) {
this.props[k.slice(4)] = v;
} else if (k === 'rft_id') {
if (/^https?:/.test(v)) {
if (this.props.domain) return;
try {
let url = new URL(v);
this.props.domain = url.hostname;
} catch {}
} else {
let match = v.match(/^info:([^/]+)\/(.+)$/);
if (match) {
this.props[match[1]] = match[2];
}
}
}
});
}
let text = this.$ref.text();
if (this.props.date) {
let numbers = this.props.date.match(/\p{Nd}+/gu);
if (numbers) {
let converted = numbers.map(n => toAscii(n));
let year = String(Math.max(...converted));
let original = numbers[converted.indexOf(year)];
this.props.year = original;
if (original !== year) {
this.props.yearAscii = year;
}
}
} else {
let match = text.match(/(?:^|\P{Nd})(\p{Nd}{4})(?!\p{Nd})/u);
if (match) {
this.props.textYear = match[1];
let ascii = toAscii(match[1]);
if (ascii !== match[1]) {
this.props.textYearAscii = ascii;
}
}
}
let link = this.$ref[0].querySelector('a.external');
if (link && link.hostname !== this.props.domain) {
this.props.linkDomain = link.hostname;
}
let match = text.match(/[^\s\p{P}].*?(?=\s*(?:\p{P}|$))/u);
if (match) {
this.props.phrase = match[0];
}
Object.freeze(this.props);
}
initRows() {
let rowClass = !this.reused && 'refrenamer-unreused';
this.$row = $('<tr>').addClass(rowClass).appendTo(dialog.$tbody);
this.$otherRow = $('<tr>').addClass(rowClass).appendTo(dialog.$otherTbody);
this.nameCell = $('<td>').addClass('refrenamer-name')
.append($('<span>').text(this.name))[0];
this.refCell = $('<td>').addClass('refrenamer-ref mw-parser-output')
.append(this.$ref)[0];
this.moveButton = new OO.ui.ButtonWidget({
framed: false,
invisibleLabel: true
}).connect(dialog, { click: ['toggleActive', this] });
this.moveCell = $('<td>').addClass('refrenamer-addremove')
.append(this.moveButton.$element)[0];
}
setActive(active) {
if (active === this.active) return;
this.active = active;
this[active ? '$otherRow' : '$row'].addClass('refrenamer-hidden');
let tooltip = getMsg(active ? 'removeTooltip' : 'addTooltip');
this.moveButton
.setFlags({ destructive: active, progressive: !active })
.setIcon(active ? 'subtract' : 'add')
.setLabel(tooltip).setTitle(tooltip);
this[active ? 'initInput' : 'initToggle']();
this[active ? '$row' : '$otherRow']
.prepend(this.nameCell, this.refCell)
.append(this.moveCell)
.removeClass('refrenamer-hidden');
}
initInput() {
if (this.input) return;
this.input = new OO.ui.MultilineTextInputWidget({
allowLinebreaks: false,
placeholder: this.name,
spellcheck: false
}).connect(dialog, { enter: ['executeAction', 'continue'] });
this.reapplyButton = new OO.ui.ButtonWidget({
classes: ['refrenamer-reapplybutton'],
framed: false,
icon: 'undo',
invisibleLabel: true,
label: getMsg('reapplyTooltip')
}).connect(dialog, {
click: ['applyConfig', [this], true]
}).connect(this.input, { click: 'focus' });
this.propsButton = new OO.ui.ButtonMenuSelectWidget({
classes: ['refrenamer-propsbutton'],
clearOnSelect: true,
framed: false,
icon: 'downTriangle',
invisibleLabel: true,
label: getMsg('propsTooltip'),
menu: {
$floatableContainer: this.input.$element,
horizontalPosition: 'end',
width: '16em'
}
});
this.propsButton.getMenu().addItems(
Object.entries(this.props).map(([k, v]) => {
let option = new OO.ui.MenuOptionWidget({
label: v,
title: v
});
option.$label.attr('data-refrenamer', k);
return option;
})
).connect(this, { choose: 'onPropChoose' });
if (!this.reused) {
this.keepCheck = new OO.ui.CheckboxInputWidget({
classes: ['refrenamer-keepcheck'],
invisibleLabel: true,
label: getMsg('keepTooltip')
}).connect(this, { change: 'onKeepChange' });
}
$('<td>').addClass('refrenamer-newname').append(
this.keepCheck?.$element,
this.input.$element,
this.reapplyButton.$element,
this.propsButton.$element
).appendTo(this.$row);
}
initToggle() {
if (this.$toggle) return;
this.$toggle = $('<button>').attr({
class: 'refrenamer-toggle oo-ui-icon-expand',
title: getMsg('expand')
}).on('click', onToggle).prependTo(this.refCell);
}
onPropChoose(item) {
this.input.insertContent(item.getTitle());
}
onKeepChange(selected) {
this.$row.toggleClass('refrenamer-kept', selected);
if (selected && !this.input.getValue()) {
dialog.applyConfig([this], true);
}
}
setupCollapsible(width) {
if (this.collapsible && Math.abs(width - this.refWidth) < 10) return;
this.refCell.classList.remove('refrenamer-collapsible');
this.collapsible = this.$ref[0].scrollHeight > this.$ref[0].clientHeight;
this.refCell.classList.toggle('refrenamer-collapsible', this.collapsible);
this.refWidth = width;
}
}
window.refRenamer = async () => {
let encodedPn = encodeURIComponent(mw.config.get('wgPageName'));
let headers = {
// 'Api-User-Agent': 'RefRenamer (https://en.wikipedia.org/wiki/User:Nardog/RefRenamer)'
};
let dependencies = [
'mediawiki.api', 'mediawiki.storage', 'mediawiki.Title',
'oojs-ui-windows', 'oojs-ui-widgets', 'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-editing-core'
];
let data = {
isEdit: !!document.getElementById('wpTextbox1') &&
!$('input[name="wpSection"]').val(),
refs: [],
templates: new Set(),
invalid: new Set()
};
try {
if (data.isEdit) {
dependencies.push('jquery.textSelection');
} else {
notify('loadingSource');
let response = await $.ajax(
mw.util.getUrl(null, { action: 'edit', venoscript: 1 }),
{ headers }
);
data.$form = $($($.parseHTML(response)).find('#editform').remove()[0]);
if (!data.$form.length) {
throw Error(getMsg('uneditableAlert'));
}
data.wikitext = data.$form.find('#wpTextbox1').val();
}
await mw.loader.using(dependencies);
if (data.isEdit) {
data.wikitext = $('#wpTextbox1').textSelection('getContents');
}
let wikitext = data.wikitext.replace(/<!--[^]*?-->/g, '');
for (let match of wikitext.matchAll(
/<ref\s+(?:name|follow)\s*=\s*(?:"\s*([^\n"]+?)\s*"?|'\s*([^\n']+?)\s*'?|([^\s>]+?))(?:\s*details\s*=\s*(?:"\s*[^\n"]+?\s*"?|'\s*[^\n']+?\s*'?|[^\s>]+?))?\s*\/?>/gi
)) {
let name = match[1] || match[2] || match[3];
let normalized = normalize(name);
let ref = data.refs.find(r => r.normalized === normalized);
if (ref) {
ref.reused = true;
ref.names.add(name);
} else {
data.refs.push(new Ref(name, normalized));
}
}
if (!data.refs.length) {
throw Error(getMsg('noNamesAlert'));
}
let response;
if (data.isEdit) {
notify('parsing');
response = await new mw.Rest().post(
'/v1/transform/wikitext/to/html/' + encodedPn,
{ wikitext: data.wikitext, body_only: true },
headers
);
} else {
notify('loadingHtml');
response = await new mw.Rest()
.get(`/v1/page/${encodedPn}/html`, null, headers);
}
let numbers = new Set();
let $page = $($.parseHTML(response));
$page.find(
'.mw-references:not([data-mw-group]) .mw-reference-text'
).each(function () {
let match = this.id.match(/^mw-reference-text-cite_note-(.+)-(\d+)$/);
if (!match) return;
let ref = data.refs.find(r => r.normalized === match[1]);
if (!ref) return;
if (ref.$ref) {
data.invalid.add(ref.name);
ref.invalid = true;
return;
}
ref.$ref = $(this);
ref.reused = ref.reused || (
ref.$ref.prev('span[rel="mw:referencedBy"]').children().length > 1
);
ref.$ref.remove().find('[id], [about]').addBack().removeAttr('id about');
ref.$ref.find('a').attr('target', '_blank')
.filter('[href^="./"]').attr('href', (_, href) => (
mw.format(mw.config.get('wgArticlePath'), href.slice(2))
));
numbers.add(match[2]);
});
let tempRe = new RegExp(`^\\./${
mw.util.escapeRegExp(mw.config.get('wgFormattedNamespaces')[10])
}:`);
$page.find(
'.mw-ref[typeof~="mw:Transclusion"], [typeof~="mw:Transclusion"] .mw-ref'
).filter(function () {
if (!/^\[\d+\]$/.test(this.textContent)) return;
let match = this.id.match(/_(\d+)-\d+$/);
return match && numbers.has(match[1]);
}).closest('[typeof~="mw:Transclusion"]').each(function () {
try {
data.templates.add(
JSON.parse(this.dataset.mw).parts[0].template.target.href
.replace(tempRe, '')
);
} catch {}
});
data.refs = data.refs.filter(ref => ref.$ref && !ref.invalid);
if (!data.refs.length) {
throw Error(getMsg('noNamesAlert'));
}
let collator;
try {
collator = Intl.Collator(mw.config.get('wgContentLanguage') + '-u-kn-true');
} catch {
collator = Intl.Collator('en-u-kn-true');
}
data.refs.sort((a, b) => collator.compare(a.name, b.name));
if (!dialog) {
initDialog();
}
dialog.open(data);
} catch (error) {
OO.ui.alert(error.message || error.responseText || error);
} finally {
if (notif) {
notif.close();
notif = null;
}
}
};
let initDialog = () => {
let rtl = document.dir === 'rtl';
let left = rtl ? 'right' : 'left';
let right = rtl ? 'left' : 'right';
mw.loader.addStyleTag(`.refrenamer .oo-ui-tabOptionWidget > .oo-ui-labelElement-label::after {
content: " (" attr(data-refrenamer) ")";
}
.refrenamer-split {
columns: 2;
}
.refrenamer-split .oo-ui-fieldLayout {
break-inside: avoid;
}
.refrenamer-split,
.refrenamer .oo-ui-layout.oo-ui-labelElement:nth-child(n+2),
.refrenamer .oo-ui-fieldLayout-align-inline .oo-ui-labelElement-label + * {
margin: 4px 0;
}
.refrenamer .oo-ui-textInputWidget > .oo-ui-inputWidget-input {
font-family: monospace,monospace;
}
.refrenamer .wikitable {
margin-bottom: 0;
overflow-wrap: break-word;
width: 100%;
}
.refrenamer-maintable .refrenamer-name,
.refrenamer .mw-reference-text,
.refrenamer-othertable .refrenamer-name::after {
font-size: 90%;
}
.refrenamer-unreused > .refrenamer-name::after {
content: "${getMsg('notReused')}";
display: inline-block;
}
.refrenamer-unreused > .refrenamer-name > span::after {
content: " ";
}
.refrenamer-hidden,
.refrenamer-unreused:not(.refrenamer-kept) > .refrenamer-newname > :not(.refrenamer-keepcheck),
.refrenamer-maintable .refrenamer-toggle,
:not(.refrenamer-collapsible) > .refrenamer-toggle {
display: none;
}
.refrenamer-name,
.refrenamer-ref {
vertical-align: top;
}
.refrenamer-maintable .refrenamer-name {
max-width: 6em;
}
.refrenamer-ref {
line-height: normal;
}
.refrenamer-newname {
height: 3em;
position: relative;
width: 12em;
}
.refrenamer-unreused:not(.refrenamer-kept) > .refrenamer-newname::after {
content: "${getMsg('tableRemove')}";
}
.refrenamer-newname > .oo-ui-textInputWidget,
.refrenamer-addremove > .oo-ui-buttonElement-frameless.oo-ui-iconElement,
.refrenamer-toggle {
margin: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.refrenamer-newname textarea {
height: 100%;
resize: none;
}
.refrenamer-keepcheck {
position: absolute;
${right}: 4px;
top: 50%;
transform: translateY(-50%);
margin: 0;
z-index: 1;
}
.refrenamer-keepcheck + .oo-ui-textInputWidget > textarea {
padding-${right}: 28px;
}
.refrenamer-newname .oo-ui-buttonElement-frameless {
position: absolute;
${right}: 0;
bottom: 0;
margin: 0;
}
.refrenamer .refrenamer-reapplybutton {
${right}: 24px;
}
.refrenamer-newname:not(:hover):not(:focus-within) > .oo-ui-buttonElement-frameless {
opacity: 0;
}
.refrenamer-newname .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button {
min-width: 24px;
min-height: 24px;
padding: 0;
}
.refrenamer-newname .oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
background-size: 16px 16px;
left: 0;
right: 0;
margin: auto;
}
.refrenamer-propsbutton .oo-ui-menuOptionWidget {
font-size: 85%;
padding: 2px 8px;
}
.refrenamer-propsbutton .oo-ui-menuOptionWidget > .oo-ui-labelElement-label::before {
content: attr(data-refrenamer);
color: var(--color-subtle, #54595d);
float: ${right};
}
.refrenamer .refrenamer-addremove {
padding: 0;
position: relative;
width: 32px;
height: 32px;
}
.refrenamer-addremove .oo-ui-buttonElement-button {
height: 100%;
}
.refrenamer-othertable {
margin: 0;
}
.refrenamer-othertable .refrenamer-name {
max-width: 16em;
}
.refrenamer-othertable .refrenamer-ref {
position: relative;
}
.refrenamer-othertable .refrenamer-collapsible {
padding-right: 20px;
}
.refrenamer-toggle {
width: 100%;
background-position: center ${right} 4px;
background-repeat: no-repeat;
background-size: 12px 12px;
background-color: transparent;
border: none;
cursor: pointer;
z-index: 1;
}
.refrenamer-expanded > .refrenamer-toggle {
width: 20px;
${left}: auto;
}
.refrenamer-toggle:hover {
background-color: var(--background-color-button-quiet--hover, rgba(0,24,73,0.027));
}
.refrenamer-othertable .mw-reference-text {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.refrenamer-othertable .refrenamer-collapsible.refrenamer-expanded > .mw-reference-text {
-webkit-line-clamp: unset;
}`);
function RefRenamerDialog(config) {
RefRenamerDialog.parent.call(this, config);
this.$element.addClass('refrenamer');
}
OO.inheritClass(RefRenamerDialog, OO.ui.ProcessDialog);
RefRenamerDialog.static.name = 'refRenamerDialog';
RefRenamerDialog.static.title = 'RefRenamer';
RefRenamerDialog.static.size = 'large';
RefRenamerDialog.static.actions = [
{
flags: ['safe', 'close'],
modes: ['main', 'mainReset', 'other', 'otherReset']
},
{
action: 'continue',
flags: ['primary', 'progressive'],
label: getMsg('continue'),
modes: ['main', 'mainReset', 'other', 'otherReset']
},
{
action: 'addAll',
flags: ['progressive'],
label: getMsg('addAll'),
modes: ['other', 'otherReset']
},
{
action: 'resetSelection',
label: getMsg('resetSelection'),
modes: ['main', 'other']
}
];
RefRenamerDialog.prototype.initialize = function () {
RefRenamerDialog.parent.prototype.initialize.apply(this, arguments);
this.warning = new OO.ui.MessageWidget({
showClose: true,
type: 'warning'
}).toggle().connect(this, { close: 'updateSize' });
this.mainSelect = new OO.ui.MenuTagMultiselectWidget({
input: { autocomplete: false },
options: [
{ data: 'aulast', label: getMsg('lastName') },
{ data: 'aufirst', label: getMsg('firstName') },
{ data: 'au', label: getMsg('author') },
{ data: 'jtitle', label: getMsg('periodical') },
{ data: 'pub|inst', label: getMsg('publisher') },
{ data: 'atitle|title', label: getMsg('article') },
{ data: 'btitle', label: getMsg('book') },
{ data: 'domain|linkDomain', label: getMsg('domain') },
{ data: 'phrase', label: getMsg('firstPhrase') }
]
}).connect(this, { change: 'updateSize', reorder: 'updateSize' });
this.lowercaseCheck = new OO.ui.CheckboxInputWidget();
this.removeDiaCheck = new OO.ui.CheckboxInputWidget();
this.removePunctCheck = new OO.ui.CheckboxInputWidget();
this.replaceSpaceCheck = new OO.ui.CheckboxInputWidget();
this.replaceSpaceLayout = new OO.ui.FieldLayout(this.replaceSpaceCheck, {
align: 'inline',
label: getMsg('replaceSpace')
});
this.replaceSpaceInput = new OO.ui.TextInputWidget({
autocomplete: false
}).toggle().connect(this, { toggle: 'updateSize' });
this.replaceSpaceCheck.connect(this.replaceSpaceInput, { change: 'toggle' });
this.replaceSpaceLayout.$header.append(this.replaceSpaceInput.$element);
this.yearCheck = new OO.ui.CheckboxInputWidget();
this.yearLayout = new OO.ui.FieldLayout(this.yearCheck, {
align: 'inline',
label: getMsg('year')
});
this.yearFallbackCheck = new OO.ui.CheckboxInputWidget();
this.yearConvertCheck = new OO.ui.CheckboxInputWidget();
this.yearConvertLayout = new OO.ui.FieldLayout(this.yearConvertCheck, {
align: 'inline',
label: getMsg('yearConvert')
});
this.latinIncrementCheck = new OO.ui.CheckboxInputWidget();
this.yearSubLayout = new OO.ui.FieldsetLayout({
items: [
new OO.ui.FieldLayout(this.yearFallbackCheck, {
align: 'inline',
label: getMsg('yearFallback')
}),
this.yearConvertLayout,
new OO.ui.FieldLayout(this.latinIncrementCheck, {
align: 'inline',
label: getMsg('latinIncrement')
})
]
}).toggle().connect(this, { toggle: 'updateSize' });
this.yearCheck.connect(this.yearSubLayout, { change: 'toggle' });
this.yearLayout.$header.append(this.yearSubLayout.$element);
this.incrementDropdown = new OO.ui.DropdownWidget({
menu: {
items: [
new OO.ui.MenuOptionWidget({ data: [1, false] }),
new OO.ui.MenuOptionWidget({ data: [2, false] }),
new OO.ui.MenuOptionWidget({ data: [0, true] }),
new OO.ui.MenuOptionWidget({ data: [1, true] })
]
}
});
this.incrementDropdown.updateLabels = () => {
let example = getMsg('incrementExample');
let delimiter = this.delimitConditionalCheck.isSelected()
? ''
: this.delimiterInput.getValue();
this.incrementDropdown.getMenu().getItems().forEach(item => {
let [start, incrementAll] = item.getData();
let label = getMsg(
'incrementExamples',
example + (incrementAll ? delimiter + start : ''),
example + delimiter + (start + Number(incrementAll))
);
item.setLabel(label);
if (item.isSelected()) {
this.incrementDropdown.setLabel(label);
}
});
};
this.delimiterInput = new OO.ui.TextInputWidget({
autocomplete: false
}).connect(this.incrementDropdown, { change: 'updateLabels' });
this.delimitConditionalCheck = new OO.ui.CheckboxInputWidget()
.connect(this.incrementDropdown, { change: 'updateLabels' });
this.removeUnreusedCheck = new OO.ui.CheckboxInputWidget();
this.applyButton = new OO.ui.ButtonInputWidget({
flags: ['primary', 'progressive'],
label: getMsg('apply'),
type: 'submit'
}).connect(this, { click: 'applyConfig' });
this.resetButton = new OO.ui.ButtonWidget({
label: getMsg('reset')
}).connect(this, { click: 'setConfig' });
this.form = new OO.ui.FormLayout({
items: [
this.warning,
new OO.ui.FieldLayout(this.mainSelect, {
align: 'top',
label: getMsg('main')
}),
new OO.ui.FieldsetLayout({
classes: ['refrenamer-split'],
items: [
new OO.ui.FieldLayout(this.lowercaseCheck, {
align: 'inline',
label: getMsg('lowercase')
}),
new OO.ui.FieldLayout(this.removeDiaCheck, {
align: 'inline',
label: getMsg('removeDia')
}),
new OO.ui.FieldLayout(this.removePunctCheck, {
align: 'inline',
label: getMsg('removePunct')
}),
this.replaceSpaceLayout,
this.yearLayout,
new OO.ui.FieldLayout(this.incrementDropdown, {
align: 'top',
label: getMsg('increment')
}),
new OO.ui.FieldLayout(this.delimiterInput, {
align: 'top',
label: getMsg('delimiter')
}),
new OO.ui.FieldLayout(this.delimitConditionalCheck, {
align: 'inline',
label: getMsg('delimitConditional')
})
]
}),
new OO.ui.FieldLayout(this.removeUnreusedCheck, {
align: 'inline',
label: getMsg('removeUnreused')
}),
this.applyButton,
this.resetButton
]
});
this.$tbody = $('<tbody>');
this.$table = $('<table>').addClass('wikitable refrenamer-maintable').append(
$('<thead>').append(
$('<tr>').append(
$('<th>').text(getMsg('tableName')),
$('<th>').text(getMsg('tableRef')),
$('<th>').text(getMsg('tableNewName')),
$('<th>').text(getMsg('tableAddRemove'))
)
),
this.$tbody
);
this.$otherTbody = $('<tbody>');
this.index = new OO.ui.IndexLayout({
autoFocus: false
}).addTabPanels([
new OO.ui.TabPanelLayout('main', {
$content: $([this.form.$element[0], this.$table[0]]),
label: getMsg('tableCaption')
}),
new OO.ui.TabPanelLayout('other', {
$content: $('<table>').addClass('wikitable refrenamer-othertable').append(
$('<thead>').append(
$('<tr>').append(
$('<th>').text(getMsg('tableName')),
$('<th>').text(getMsg('tableRef')),
$('<th>').text(getMsg('tableAddRemove'))
)
),
this.$otherTbody
),
label: getMsg('otherTableCaption')
})
]).on('set', () => {
let mode = this.index.getCurrentTabPanelName();
if (this.refs.every(ref => ref.active === ref.isAuto)) {
mode += 'Reset';
}
this.actions.setMode(mode);
this.updateSize();
this.setupCollapsibles();
});
this.$body.append(this.index.$element);
this.defaults = {
main: ['aulast', 'aufirst', 'au', 'jtitle', 'pub|inst', 'phrase'],
lowercase: false,
removeDia: false,
removePunct: false,
replaceSpace: false,
year: true,
yearFallback: false,
yearConvert: true,
latinIncrement: true,
increment: 2,
incrementAll: false,
delimiter: '-',
delimitConditional: false,
removeUnreused: true
};
};
RefRenamerDialog.prototype.getConfig = function () {
let incrementData = this.incrementDropdown.getMenu()
.findSelectedItem().getData();
return {
main: this.mainSelect.getValue(),
lowercase: this.lowercaseCheck.isSelected(),
removeDia: this.removeDiaCheck.isSelected(),
removePunct: this.removePunctCheck.isSelected(),
replaceSpace: this.replaceSpaceCheck.isSelected() &&
this.replaceSpaceInput.getValue(),
year: this.yearCheck.isSelected(),
yearFallback: this.yearFallbackCheck.isSelected(),
yearConvert: this.yearConvertCheck.isSelected(),
latinIncrement: this.latinIncrementCheck.isSelected(),
increment: incrementData[0],
incrementAll: incrementData[1],
delimiter: this.delimiterInput.getValue(),
delimitConditional: this.delimitConditionalCheck.isSelected(),
removeUnreused: this.removeUnreusedCheck.isSelected()
};
};
RefRenamerDialog.prototype.setConfig = function (config) {
config = Object.assign({}, this.defaults, config);
this.mainSelect.setValue(config.main);
this.lowercaseCheck.setSelected(config.lowercase);
this.removeDiaCheck.setSelected(config.removeDia);
this.removePunctCheck.setSelected(config.removePunct);
let replaceSpace = typeof config.replaceSpace === 'string';
this.replaceSpaceCheck.setSelected(replaceSpace);
this.replaceSpaceInput
.setValue(replaceSpace ? config.replaceSpace : '-');
this.yearCheck.setSelected(config.year);
this.yearFallbackCheck.setSelected(config.yearFallback);
this.yearConvertCheck.setSelected(config.yearConvert);
this.latinIncrementCheck.setSelected(
// compatibility
typeof config.latinIncrement === 'number' || config.latinIncrement
);
let incrementMenu = this.incrementDropdown.getMenu();
let incrementItem = incrementMenu
.findItemFromData([config.increment, config.incrementAll]);
if (incrementItem) {
incrementMenu.selectItem(incrementItem);
} else {
incrementMenu.selectItemByData([2, false]);
}
this.delimiterInput.setValue(config.delimiter);
this.delimitConditionalCheck.setSelected(config.delimitConditional);
this.removeUnreusedCheck.setSelected(config.removeUnreused);
};
RefRenamerDialog.prototype.applyConfig = function (refs, forceKeep) {
refs = (refs || this.refs).filter(ref => ref.active);
if (!refs.length) return;
let config = this.getConfig(), withYear = new Set();
let stack = config.main.flatMap(k => k.split('|'));
let names = refs.map(ref => {
if (!forceKeep && ref.keepCheck) {
ref.keepCheck.setSelected(!config.removeUnreused);
if (config.removeUnreused) return;
}
let exports = {
name: ref.name,
props: ref.props,
getElement: () => ref.$ref.clone()[0]
};
mw.hook('refrenamer.rename').fire(exports);
if (typeof exports.newName === 'string') {
return exports.newName;
}
let s;
stack.some(k => (s = ref.props[k]));
if (!s) return;
if (config.lowercase) {
s = s.toLowerCase();
}
if (config.removeDia) {
s = s.normalize('NFD').replace(/\p{Mn}/gu, '');
}
if (config.removePunct) {
s = s.replace(/\p{P}/gu, '');
}
if (typeof config.replaceSpace === 'string') {
s = s.replace(/\s+/g, config.replaceSpace);
}
let year = config.year && (
config.yearConvert && ref.props.yearAscii ||
ref.props.year ||
config.yearFallback && (
config.yearConvert && ref.props.textYearAscii ||
ref.props.textYear
)
);
if (year) {
let delimiter = config.delimitConditional && /\P{Nd}$/u.test(s)
? ''
: config.delimiter;
s += delimiter + year;
withYear.add(ref);
}
return s;
});
let comps = names.map(s => s && normalize(s));
let hardComps = new Set();
this.refs.forEach(ref => {
if (refs.includes(ref)) return;
hardComps.add(
ref.active && normalize(ref.input.getValue()) ||
ref.normalized
);
});
refs.forEach((ref, i) => {
let s = names[i];
if (!s) {
ref.input.setValue('');
return;
}
let normalized = comps[i];
let useLatin = config.latinIncrement && withYear.has(ref);
let needsIncrement = hardComps.has(normalized) ||
comps.indexOf(normalized) !== i ||
(useLatin || config.incrementAll) &&
comps.slice(i + 1).includes(normalized);
if (needsIncrement) {
let unsuffixed = s;
let delimiter = useLatin || (
config.delimitConditional && /\P{Nd}$/u.test(s)
) ? '' : config.delimiter;
let increment = useLatin ? 0 : config.increment;
do {
s = unsuffixed + delimiter +
(useLatin ? toLatin(increment) : increment);
normalized = normalize(s);
increment++;
} while (hardComps.has(normalized) || comps.includes(normalized));
comps.push(normalized);
}
ref.input.setValue(s);
});
};
RefRenamerDialog.prototype.setActive = function (refs, active) {
refs.forEach(ref => {
ref.setActive(active === undefined ? ref.isAuto : active);
});
this.applyConfig(refs);
let activeCount = this.refs.filter(ref => ref.active).length;
let inactiveCount = this.refs.length - activeCount;
let tabs = this.index.getTabs().getItems();
tabs[0].$label.attr('data-refrenamer', activeCount);
tabs[1].setDisabled(!inactiveCount)
.$label.attr('data-refrenamer', inactiveCount);
this.$table.toggleClass('refrenamer-hidden', !activeCount);
if (!activeCount) {
this.index.setTabPanel('other');
} else if (!inactiveCount) {
this.index.setTabPanel('main');
} else {
this.index.emit('set');
}
};
RefRenamerDialog.prototype.toggleActive = function (ref) {
this.setActive([ref], !ref.active);
};
RefRenamerDialog.prototype.getSetupProcess = function (data) {
Object.assign(this, data);
let warnings = [];
if (this.templates.size) {
warnings.push(
document.createTextNode(getMsg('templatesWarn')),
$('<ul>').append(
[...this.templates].map(s => $('<li>').append(
'{{',
$('<a>').attr({
href: mw.Title.newFromText('Template:' + s).getUrl(),
target: '_blank'
}).text(s),
'}}'
))
)[0]
);
}
if (this.invalid.size) {
warnings.push(
document.createTextNode(getMsg('invalidWarn')),
$('<ul>').append(
[...this.invalid].map(s => $('<li>').text(s))
)[0]
);
}
this.warning.setLabel(warnings.length ? $(warnings) : '')
.toggle(warnings.length);
this.setConfig(mw.storage.getObject('refrenamer'));
this.$tbody.empty();
this.$otherTbody.empty();
this.refs.forEach(ref => {
ref.initProps();
ref.initRows();
});
this.setActive(this.refs);
let hasNonAsciiYear = this.refs.some(ref => (
ref.props.yearAscii || ref.props.textYearAscii
));
this.yearConvertLayout.toggle(hasNonAsciiYear);
this.index.getCurrentTabPanel().$element.scrollTop(0);
return RefRenamerDialog.super.prototype.getSetupProcess.call(this, data).next(function () {
this.index.emit('set');
}, this);
};
RefRenamerDialog.prototype.setupCollapsibles = mw.util.debounce(function () {
if (this.index.getCurrentTabPanelName() !== 'other') return;
let now = performance.now();
let refs = this.refs.filter(ref => !ref.active && ref.$toggle);
let width = refs[0].refCell.clientWidth;
refs.reverse().forEach(ref => {
ref.setupCollapsible(width);
});
console.log(performance.now() - now);
this.updateSize();
}, 200);
RefRenamerDialog.prototype.getActionProcess = function (action) {
if (action === 'addAll') {
this.setActive(this.refs, true);
} else if (action === 'resetSelection') {
this.setActive(this.refs);
}
return RefRenamerDialog.super.prototype.getActionProcess.call(this, action).next(function () {
if (action !== 'continue') return;
let subs = {}, integers = [], dupes = [];
let comps = this.refs.filter(ref => !ref.active).map(ref => ref.normalized);
let hasNonVe;
this.refs.forEach(ref => {
if (!ref.active) return;
if (ref.keepCheck && !ref.keepCheck.isSelected()) {
subs[ref.normalized] = null;
hasNonVe = hasNonVe || !ref.isVe;
return;
}
let newName = ref.input.getValue().replace(/^[\s_]+|[\s_]+$/g, '');
let normalized = normalize(newName);
if (!normalized || newName === ref.name && ref.names.size === 1) {
comps.push(ref.normalized);
} else if (!/\D/.test(normalized)) {
integers.push(newName);
} else if (comps.includes(normalized)) {
dupes.push(newName);
} else {
subs[ref.normalized] = newName;
hasNonVe = hasNonVe || !ref.isVe;
comps.push(normalized);
}
});
if (integers.length || dupes.length) {
return new OO.ui.Error($([
[integers, 'numericError'],
[dupes, 'duplicatesError']
].flatMap(([names, msgKey]) => names.length ? [
document.createTextNode(getMsg(msgKey)),
$('<ul>').append(names.map(n => $('<li>').text(n)))[0]
] : [])), { recoverable: false });
}
if (!Object.keys(subs).length) {
return new OO.ui.Error(
getMsg('noChangesError'),
{ recoverable: false }
);
}
this.close();
let newText = this.wikitext.replace(
/<(ref)\s+(name|follow)\s*=\s*(?:"\s*([^\n"]+?)\s*"?|'\s*([^\n']+?)\s*'?|([^\s>]+?))((?:\s*details\s*=\s*(?:"\s*[^\n"]+?\s*"?|'\s*[^\n']+?\s*'?|[^\s>]+?))?\s*\/?)>/gi,
(s, tag, attr, name1, name2, name3, slash) => {
let normalized = normalize(name1 || name2 || name3);
return Object.hasOwn(subs, normalized)
? subs[normalized]
? `<${tag} ${attr}="${subs[normalized]}"${slash}>`
: `<${tag}>`
: s;
}
);
let iw = mw.config.get('wgWikiID') === 'enwiki' ? '' : 'w:en:';
let summary = hasNonVe
? getMsg('genericSummary', iw + 'User:Nardog/RefRenamer')
: getMsg(
'summary',
iw + 'Wikipedia:VisualEditor/Named references',
iw + 'User:Nardog/RefRenamer'
);
if (this.isEdit) {
$('#wpTextbox1').textSelection('setContents', newText);
if (document.documentElement.classList.contains('ve-active')) {
ve.init.target.once('showChanges', () => {
ve.init.target.saveDialog.reviewModeButtonSelect.selectItemByData('source');
ve.init.target.saveDialog.setEditSummary(summary);
});
ve.init.target.showSaveDialog('review');
} else {
let $summary = $('#wpSummary');
let oldSummary = $summary.textSelection('getContents').trim();
if (oldSummary) {
summary = /^\/\*.+\*\/$/.test(oldSummary)
? oldSummary + ' ' + summary
: oldSummary + getMsg('summarySeparator') + summary;
}
$summary.textSelection('setContents', summary);
$('#wpDiff').trigger('click');
}
} else {
this.$form.find('#wpTextbox1').val(newText).end()
.find('#wpSummary').val(summary).end()
.find('#wpMinoredit').prop('checked', true).end()
.appendTo(document.body)
.find('#wpDiff').trigger('click').end()
.remove();
notify('opening');
}
let customized = Object.entries(this.getConfig())
.filter(([k, v]) => String(v) !== String(this.defaults[k]));
if (customized.length) {
mw.storage.setObject('refrenamer', Object.fromEntries(customized), 7776000);
} else {
mw.storage.remove('refrenamer');
}
}, this);
};
RefRenamerDialog.prototype.hideErrors = function () {
RefRenamerDialog.super.prototype.hideErrors.call(this);
this.actions.setAbilities({ continue: true });
};
RefRenamerDialog.prototype.getBodyHeight = function () {
return this.index.$menu[0].scrollHeight +
this.index.getCurrentTabPanel().$element[0].scrollHeight;
};
dialog = new RefRenamerDialog();
window.refRenamerDialog = dialog;
let winMan = new OO.ui.WindowManager();
winMan.addWindows([dialog]);
winMan.$element.appendTo(OO.ui.getTeleportTarget());
};
let normalize = s => s.replace(/[\s_]+/g, '_').replace(/^_+|_+$/g, '');
let toAscii = s => s.replace(/\D/g, c => {
let cp = c.codePointAt(0);
let zero = cp;
while (/\p{Nd}/u.test(String.fromCodePoint(zero - 1))) {
zero--;
}
return (cp - zero) % 10;
});
let toLatin = n => {
let s = '';
do {
s = String.fromCharCode(97 + (n % 26)) + s;
n = Math.floor(n / 26) - 1;
} while (n >= 0);
return s;
};
let onToggle = function () {
let expanded = this.parentElement.classList.toggle('refrenamer-expanded');
let newAction = expanded ? 'collapse' : 'expand';
this.className = 'refrenamer-toggle oo-ui-icon-' + newAction;
this.title = getMsg(newAction);
dialog.updateSize();
};
window.refRenamer();
})();
i9752tj55lovypi4t3e8hkb1a739riy
User:Nardog/sandbox8.js
2
152797
783531
766921
2026-11-08T16:29:37Z
Nardog
40946
783531
javascript
text/javascript
$('.catchangesviewer').remove();
$('head > style').filter(function () {
return this.textContent.includes('.catchangesviewer .oo-ui-numberInputWidget');
}).remove();
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter',
'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.UserInputWidget',
'mediawiki.widgets.datetime', 'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-movement', 'mediawiki.interface.helpers.styles',
'user.options'
], async require => {
mw.loader.addStyleTag(`.catchangesviewer .oo-ui-numberInputWidget {
width: 4em;
}
.catchangesviewer .oo-ui-numberInputWidget input {
text-align: center;
}
.catchangesviewer .oo-ui-menuSelectWidget,
.catchangesviewer .mw-widgets-datetime-dateTimeInputWidget {
width: min-content;
}
.catchangesviewer .mw-widget-userInputWidget {
width: 8em;
}
.catchangesviewer .oo-ui-fieldLayout-align-inline {
vertical-align: top;
}
.catchangesviewer-table {
white-space: nowrap;
}
.catchangesviewer-addition {
background: var(--background-color-success-subtle, #d5fdf4);
}
.catchangesviewer-removal {
background: var(--background-color-error-subtle, #fee7e6);
}
.catchangesviewer-table td:empty::after {
content: "\\a0";
}`);
let api = new mw.Api({
// userAgent: 'CatChangesViewer (https://en.wikipedia.org/wiki/User:Nardog/CatChangesViewer)'
});
let { formatTimeAndDate } = require('mediawiki.DateFormatter');
let msgKeys = mw.config.get('wgContentLanguage') === 'en' ? [] : [
'recentchanges-page-added-to-category',
'recentchanges-page-added-to-category-bundled',
'recentchanges-page-removed-from-category',
'recentchanges-page-removed-from-category-bundled'
];
let addedKeys = msgKeys.slice(0, 2), removedKeys = msgKeys.slice(2);
class CatChangesSearch {
constructor() {
this.options = getOptions();
this.params = Object.assign({
action: 'query',
list: 'recentchanges',
rctype: 'categorize',
rctitle: mw.config.get('wgPageName'),
rcprop: 'ids|timestamp|comment|user|flags',
formatversion: 2
}, this.options);
this.rcs = [];
this.latest = {};
this.curPage = 0;
this.titles = {};
this.newRcs = [];
}
async load(isRefresh) {
isRefresh = isRefresh && !!this.rcs.length;
if (isRefresh) {
this.params.rcdir = 'newer';
this.params.rclimit = Math.min(limitInput.getNumericValue() + 1, 500);
this.params.rccontinue = this.rcs[0].timestamp.replace(/\D/g, '') + '|' + this.rcs[0].revid;
} else {
delete this.params.rcdir;
this.params.rclimit = limitInput.getNumericValue();
this.params.rccontinue = this.rccontinue;
}
this.setDisabledAll(true);
$error.empty();
let msgPromise = api.loadMessagesIfMissing(msgKeys);
try {
let response = await api.get(this.params);
if (!isRefresh) {
this.rccontinue = response.continue?.rccontinue;
this.complete = !this.rccontinue && response.batchcomplete;
}
await msgPromise;
this.processChanges(isRefresh, response.query.recentchanges);
} catch (e) {
$error.text(e.error?.info || e);
console.error(e);
} finally {
this.setDisabledAll(false);
this.resetNavButtons();
this.updateButton();
refreshButton.toggle(true);
}
}
updateButton() {
button.setLabel(
this.rcs.length
? this.complete ? 'No more results' : 'Load more'
: this.complete ? 'No results' : 'Search'
).setDisabled(this.complete);
}
processChanges(isRefresh, rcs = []) {
if (isRefresh && rcs[0]?.revid === this.rcs[0].revid) {
rcs.shift();
}
if (!rcs.length) return;
rcs.forEach(rc => {
if (!rc.comment) return;
let page = rc.comment.match(/\[\[:?([^|\]]+)\]\]/)[1];
if (rc.comment.includes(']] added to category')) {
rc.action = 'addition';
} else if (rc.comment.includes(']] removed from category')) {
rc.action = 'removal';
} else if (addedKeys.some(key => rc.comment === mw.msg(key, page))) {
rc.action = 'addition';
} else if (removedKeys.some(key => rc.comment === mw.msg(key, page))) {
rc.action = 'removal';
}
if (Object.hasOwn(this.latest, page)) {
if (isRefresh) {
this.latest[page].duplicate = true;
this.latest[page] = rc;
} else {
rc.duplicate = true;
}
} else {
this.latest[page] = rc;
}
this.rcs[isRefresh ? 'unshift' : 'push'](rc);
this.addRow(rc, page);
});
this.initNav();
this.queryTitles();
}
initNav() {
let rcsToShow = hideAdditionsCheckbox.isSelected()
? this.rcs.filter(rc => rc.action !== 'addition')
: hideRemovalsCheckbox.isSelected()
? this.rcs.filter(rc => rc.action !== 'removal')
: this.rcs;
if (hideDuplicatesCheckbox.isSelected()) {
rcsToShow = rcsToShow.filter(rc => !rc.duplicate);
}
this.visibleRows = rcsToShow.map(rc => rc.$row[0]);
this.pageCount = Math.ceil(this.visibleRows.length / perPageNum) || 1;
let z = this.rcs.length > perPageNum
? perPageNum * this.pageCount - this.visibleRows.length
: this.rcs.length - this.visibleRows.length;
for (let i = 0; i < z; i++) {
this.visibleRows.push(
$('<tr>').append('<td>', '<td>', '<td>', '<td>', '<td>')[0]
);
}
if (!this.$table) {
this.$tbody = $('<tbody>');
this.$table = $('<table>').addClass('wikitable catchangesviewer-table').append(
$('<thead>').append(
$('<tr>').append(
$('<th>').text('±'),
$('<th>').text('Date'),
$('<th>').text('Page'),
$('<th>').text('User'),
$('<th>').text('Bot')
)
),
this.$tbody
);
}
this.setPage();
navLayout.toggle(true).$element.before(this.$table);
}
setPage(increment) {
if (this.pageCount > 1) {
if (increment === 'first') {
this.curPage = 0;
} else if (increment === 'last') {
this.curPage = this.pageCount - 1;
} else if (increment) {
this.curPage += increment;
if (this.curPage < 0) {
this.curPage = this.pageCount - 1;
}
if (this.curPage > this.pageCount - 1) {
this.curPage = 0;
}
} else if (this.curPage > this.pageCount - 1) {
this.curPage = this.pageCount - 1;
}
} else {
this.curPage = 0;
}
let start = this.curPage * perPageNum;
this.$tbody.children().detach();
this.$tbody.append(this.visibleRows.slice(start, start + perPageNum));
navLabel.setLabel(`${this.curPage + 1} / ${this.pageCount}`);
this.resetNavButtons();
}
resetNavButtons() {
firstButton.setDisabled(this.curPage === 0);
prevButton.setDisabled(this.pageCount < 2);
nextButton.setDisabled(this.pageCount < 2);
lastButton.setDisabled(this.curPage === this.pageCount - 1);
}
setDisabledAll(disabled) {
[
limitInput, filtersButton, userInput, untilInput, button,
refreshButton, firstButton, prevButton, nextButton, lastButton,
hideAdditionsCheckbox, hideRemovalsCheckbox, hideDuplicatesCheckbox
].forEach(widget => {
widget.setDisabled(disabled);
});
}
addRow(rc, page) {
let symbol = rc.action === 'addition' ? '+' : rc.action === 'removal' ? '−' : '?';
let $userLink = this.makeLink(
(rc.anon || rc.temp ? 'Special:Contributions/' : 'User:') + rc.user,
rc.user
);
if (rc.temp) {
$userLink.addClass('mw-tempuserlink').attr('data-mw-target', rc.user);
}
rc.$row = $('<tr>').attr({
class: rc.action ? 'catchangesviewer-' + rc.action : null,
'data-mw-revid': rc.revid
}).append(
$('<td>').text(symbol),
$('<td>').append(
$('<a>').attr('href', mw.util.getUrl(page, {
oldid: rc.revid
})).text(formatTimeAndDate(new Date(rc.timestamp))),
' ',
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append(
$('<a>').attr('href', mw.util.getUrl(page, {
diff: rc.revid
})).text('diff')
),
$('<span>').append(
$('<a>').attr('href', mw.util.getUrl(page, {
curid: rc.pageid,
action: 'history'
})).text('hist')
)
)
),
$('<td>').append(this.makeLink(page)),
$('<td>').append(
uicEnabled && require('ext.checkUser.userInfoCard').createButton(rc.user),
$userLink,
' ',
$('<span>').addClass('mw-changeslist-links').append(
$('<span>').append(
this.makeLink('User talk:' + rc.user, 'talk')
),
!rc.anon && !rc.temp && $('<span>').append(
this.makeLink('Special:Contributions/' + rc.user, 'contribs')
)
)
),
$('<td>').text(rc.bot ? 'Yes' : 'No')
);
this.newRcs.push(rc);
}
makeLink(title, text) {
let obj;
if (Object.hasOwn(this.titles, title)) {
obj = this.titles[title];
} else {
obj = { links: [] };
this.titles[title] = obj;
}
let params = obj.red && { action: 'edit', redlink: 1 } ||
obj.redirect && { redirect: 'no' };
let $link = $('<a>').attr({
href: mw.util.getUrl(obj.canonical || title, params),
title: obj.canonical || title
}).addClass(obj.classes).text(text || title);
if (!obj.processed) {
obj.links.push($link[0]);
}
return $link;
}
async queryTitles() {
let titles = Object.entries(this.titles)
.filter(([, v]) => !v.processed).map(([k]) => k);
try {
while (titles.length) {
let curTitles = titles.slice(0, 50);
curTitles.forEach(title => {
this.titles[title].processed = true;
});
let { query } = await api.post({
action: 'query',
titles: curTitles,
prop: 'info',
inprop: 'linkclasses',
inlinkcontext: mw.config.get('wgPageName'),
formatversion: 2
}, {
headers: { 'Promise-Non-Write-API-Action': 1 }
});
query.normalized?.forEach(({ from, to }) => {
if (!Object.hasOwn(this.titles, from)) return;
let obj = this.titles[from];
obj.canonical = to;
this.titles[to] = obj;
});
query.pages?.forEach(p => {
if (!Object.hasOwn(this.titles, p.title)) return;
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;
}
});
curTitles.forEach(title => {
let obj = this.titles[title];
$(obj.links).addClass(obj.classes).attr({
href: mw.util.getUrl(
obj.canonical || title,
obj.red && { action: 'edit', redlink: 1 }
),
title: obj.canonical
});
delete obj.links;
});
titles.splice(0, 50);
}
} finally {
this.fireHook();
}
}
fireHook() {
if (!this.newRcs.length) return;
let tempRows = this.newRcs.map(rc => rc.$row.clone()[0]);
let $tempTable = $('<table>').hide()
.addClass('catchangesviewer-table catchangesviewer-temptable')
.append(tempRows)
.insertAfter(this.$table);
mw.hook('wikipage.content').fire($tempTable);
this.newRcs.forEach((rc, i) => {
rc.$row.html(tempRows[i].children);
});
$tempTable.remove();
this.newRcs = [];
}
destroy() {
this.$table?.remove();
navLayout.toggle(false);
}
}
let curSearch;
let getOptions = () => {
let options = {};
Object.entries(filters).forEach(([k, v]) => {
if (v.widget.getIcon() === 'check') {
if (v.input) {
let value = v.input.getValue();
if (value) {
options[k] = value;
}
} else {
options.rcshow = options.rcshow || [];
options.rcshow.push(k);
}
}
});
return options;
};
let isModified = () => {
if (!curSearch) return false;
let options = getOptions();
return ['rcshow', 'rcuser', 'rcexcludeuser', 'rcstart'].some(k => (
String(options[k]) !== String(curSearch.options[k])
));
};
let updateButton = () => {
if (isModified()) {
button.setLabel('Search').setDisabled(false);
} else if (curSearch) {
curSearch.updateButton();
}
};
let perPageNum = window.catchangesviewerChangesPerPage || 20;
let limitInput = new OO.ui.NumberInputWidget({
max: 500,
min: 1,
required: true,
showButtons: false,
title: 'Number of changes to load (1–500)',
value: window.catchangesviewerDefaultLimit || 50
}).setIndicator();
let userInput = new mw.widgets.UserInputWidget({
placeholder: 'User'
}).on('change', updateButton).toggle();
let untilInput = new mw.widgets.datetime.DateTimeInputWidget({
clearable: false,
min: new Date(Date.now() - 2592000000)
}).on('change', updateButton).toggle();
let filters = {
'!anon': {
widget: new OO.ui.MenuOptionWidget({
data: '!anon',
label: 'Registered only',
icon: 'none'
}),
incompatibleWith: 'anon'
},
anon: {
widget: new OO.ui.MenuOptionWidget({
data: 'anon',
label: 'Unregistered only',
icon: 'none'
}),
incompatibleWith: '!anon'
},
'!bot': {
widget: new OO.ui.MenuOptionWidget({
data: '!bot',
label: 'No bots',
icon: 'none'
}),
incompatibleWith: 'bot'
},
bot: {
widget: new OO.ui.MenuOptionWidget({
data: 'bot',
label: 'Bots only',
icon: 'none'
}),
incompatibleWith: '!bot'
},
rcuser: {
widget: new OO.ui.MenuOptionWidget({
data: 'rcuser',
label: 'This user:',
icon: 'none'
}),
incompatibleWith: 'rcexcludeuser',
input: userInput
},
rcexcludeuser: {
widget: new OO.ui.MenuOptionWidget({
data: 'rcexcludeuser',
label: 'Not this user:',
icon: 'none'
}),
incompatibleWith: 'rcuser',
input: userInput
},
rcstart: {
widget: new OO.ui.MenuOptionWidget({
data: 'rcstart',
label: 'Until:',
icon: 'none'
}),
input: untilInput
}
};
let filtersButton = new OO.ui.ButtonMenuSelectWidget({
icon: 'funnel',
menu: { items: Object.values(filters).map(o => o.widget) },
invisibleLabel: true,
label: 'Filters'
});
filtersButton.getMenu().on('choose', option => {
let data = filters[option.getData()];
if (option.getIcon() === 'none') {
option.setIcon('check');
if (data.incompatibleWith) {
filters[data.incompatibleWith].widget.setIcon('none');
}
filtersButton.setIndicator('required');
if (data.input) {
data.input.toggle(true);
}
} else {
option.setIcon('none');
if (!Object.values(filters).some(o => o.widget.getIcon() === 'check')) {
filtersButton.setIndicator();
}
if (data.input) {
data.input.toggle(false);
}
}
updateButton();
});
let button = new OO.ui.ButtonInputWidget({
flags: ['primary', 'progressive'],
label: 'Search',
type: 'submit'
}).on('click', () => {
if (curSearch) {
if (isModified()) {
curSearch.destroy();
curSearch = new CatChangesSearch();
}
} else {
curSearch = new CatChangesSearch();
}
curSearch.load();
window.ccv = curSearch;
});
let refreshButton = new OO.ui.ButtonWidget({
icon: 'reload',
invisibleLabel: true,
label: 'Load new'
}).toggle().on('click', () => {
curSearch.load(true);
});
let form = new OO.ui.FormLayout({
classes: ['oo-ui-horizontalLayout'],
items: [
limitInput, filtersButton, userInput, untilInput, button, refreshButton
]
});
let navLabel = new OO.ui.LabelWidget();
let firstButton = new OO.ui.ButtonWidget({
icon: 'first',
invisibleLabel: true,
label: 'Newest ' + perPageNum
}).on('click', () => {
curSearch.setPage('first');
});
let prevButton = new OO.ui.ButtonWidget({
icon: 'previous',
invisibleLabel: true,
label: 'Newer ' + perPageNum
}).on('click', () => {
curSearch.setPage(-1);
});
let nextButton = new OO.ui.ButtonWidget({
icon: 'next',
invisibleLabel: true,
label: 'Older ' + perPageNum
}).on('click', () => {
curSearch.setPage(1);
});
let lastButton = new OO.ui.ButtonWidget({
icon: 'last',
invisibleLabel: true,
label: 'Oldest ' + perPageNum
}).on('click', () => {
curSearch.setPage('last');
});
let hideAdditionsCheckbox = new OO.ui.CheckboxInputWidget().on('change', selected => {
if (selected) {
hideRemovalsCheckbox.setSelected(false, true);
}
curSearch.initNav();
});
let hideRemovalsCheckbox = new OO.ui.CheckboxInputWidget().on('change', selected => {
if (selected) {
hideAdditionsCheckbox.setSelected(false, true);
}
curSearch.initNav();
});
let hideDuplicatesCheckbox = new OO.ui.CheckboxInputWidget().on('change', () => {
curSearch.initNav();
});
let navLayout = new OO.ui.HorizontalLayout({
items: [
navLabel,
new OO.ui.ButtonGroupWidget({
items: [firstButton, prevButton, nextButton, lastButton]
}),
new OO.ui.HorizontalLayout({
items: [
new OO.ui.LabelWidget({ label: 'Hide:' }),
new OO.ui.FieldLayout(hideAdditionsCheckbox, {
align: 'inline',
label: 'Additions'
}),
new OO.ui.FieldLayout(hideRemovalsCheckbox, {
align: 'inline',
label: 'Removals'
}),
new OO.ui.FieldLayout(hideDuplicatesCheckbox, {
align: 'inline',
label: 'Duplicates'
})
]
})
]
}).toggle();
let $error = $('<div>');
let $div = $('<div>').addClass('catchangesviewer').append(
$('<h2>').text('Recent changes'), navLayout.$element, form.$element, $error
);
$(() => {
$('.mw-category-generated').first().before($div);
});
let modules = [];
let tempEnabled = !!Number(mw.user.options.get('checkuser-temporary-account-enable'));
if (tempEnabled) {
modules.push('ext.checkUser.tempAccounts', 'ext.checkUser.styles');
}
let uicEnabled = !!Number(mw.user.options.get('checkuser-userinfocard-enable'));
if (uicEnabled) {
modules.push('ext.checkUser.userInfoCard');
}
if (Number(mw.user.options.get('ipinfo-beta-feature-enable'))) {
modules.push('ext.ipInfo', 'ext.ipInfo.styles');
}
await mw.loader.using(modules);
if (!tempEnabled) return;
// https://gerrit.wikimedia.org/g/mediawiki/core/+/master/resources/src/startup/mediawiki.loader.js
let makeRequire = (moduleObj, basePath) => moduleName => {
let relParts = moduleName.match(/^((?:\.\.?\/)+)(.*)$/);
if (!relParts) {
return mw.loader.require(moduleName);
}
let baseDirParts = basePath.split('/');
baseDirParts.pop();
let prefixes = relParts[1].split('/');
prefixes.pop();
let prefix;
while ((prefix = prefixes.pop())) {
if (prefix === '..') {
baseDirParts.pop();
}
}
let fileName = baseDirParts.join('/');
if (fileName) {
fileName += '/';
}
fileName += relParts[2];
if (Object.hasOwn(moduleObj.packageExports, fileName)) {
return moduleObj.packageExports[fileName];
}
let scriptFiles = moduleObj.script.files;
if (!Object.hasOwn(scriptFiles, fileName)) {
throw Error('Cannot require undefined file ' + fileName);
}
let result;
let fileContent = scriptFiles[fileName];
if (typeof fileContent === 'function') {
let moduleParam = { exports: {} };
fileContent(makeRequire(moduleObj, fileName), moduleParam, moduleParam.exports);
result = moduleParam.exports;
} else {
result = fileContent;
}
moduleObj.packageExports[fileName] = result;
return result;
};
try {
let pack = mw.loader.moduleRegistry['ext.checkUser.tempAccounts'];
makeRequire(pack, pack.script.main)('./initOnHook.js')();
} catch {}
});
8h0e0gjc29we47l76x08mf87i60bx2v
User:DVRTed/sandbox2
2
166741
783487
783462
2026-11-08T13:40:10Z
DVRTed
55993
Updated status for [[Test Page 817876]] to tagged (using [[User:DVRTed/AINB-helper|AINB-helper]])
783487
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=test|status=unnecessary|notes=customnotes}}
{{AIC article row|article=Bob Morley|status=completed|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=tagged|notes=great! {{slink [[special:diff/783485|AI tagged]]}}---}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=tagged|notes=6 edits: [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783485|AI tagged]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
g8ngq9flzc2ru662h86yba277qg5esl
783489
783487
2026-11-08T13:42:24Z
DVRTed
55993
Batch edited 4 rows: 2 completed, 1 ongoing, 1 unnecessary (using [[User:DVRTed/AINB-helper|AINB-helper]])
783489
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=test|status=completed|notes=customnotes}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=great! {{slink [[special:diff/783485|AI tagged]]}}---}}
{{AIC article row|article=Testpage12|status=unnecessary|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=tagged|notes=6 edits: [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783485|AI tagged]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
s55uy681kvhme8za9tj5lqb258voyjb
783490
783489
2026-11-08T13:42:34Z
DVRTed
55993
Updated status for [[Test Page 817876]] to unnecessary (using [[User:DVRTed/AINB-helper|AINB-helper]])
783490
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=test|status=completed|notes=customnotes}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=great! {{slink [[special:diff/783485|AI tagged]]}}---}}
{{AIC article row|article=Testpage12|status=unnecessary|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=6 edits: [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783485|AI tagged]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
28nj0paejsyxmy8d7ux863fsr0xybr6
783491
783490
2026-11-08T13:43:21Z
DVRTed
55993
783491
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=test|status=unnecessary|notes=customnotes}}
{{AIC article row|article=Bob Morley|status=completed|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=great! {{slink}}---}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=completed|notes=6 edits: [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
j8h2sbb2efc5gifv2vu9dmjll15spv1
783496
783491
2026-11-08T13:51:41Z
DVRTed
55993
Updated status for [[Test Page 817876]] to tagged (using [[User:DVRTed/AINB-helper|AINB-helper]])
783496
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=test|status=unnecessary|notes=customnotes}}
{{AIC article row|article=Bob Morley|status=completed|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=tagged|notes= [[special:diff/783494|AI tagged]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=tagged|notes= [[special:diff/783494|AI tagged]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
tqz92cucqwhnako4fqxfa4hqcdoyojq
783497
783496
2026-11-08T13:52:33Z
DVRTed
55993
783497
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=test|status=unnecessary|notes=customnotes}}
{{AIC article row|article=Bob Morley|status=completed|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=foo {{slink}}}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=completed|notes=6 edits: [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=bar {{slink}}}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
286h7a1hfwcqdf6ptzfm5wyo3c9e7k8
783500
783497
2026-11-08T13:53:07Z
DVRTed
55993
Updated status for [[Test Page 817876]] to tagged (using [[User:DVRTed/AINB-helper|AINB-helper]])
783500
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=test|status=unnecessary|notes=customnotes}}
{{AIC article row|article=Bob Morley|status=completed|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=tagged|notes= [[special:diff/783498|AI tagged]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=tagged|notes= [[special:diff/783498|AI tagged]]}}
{{AIC article row|article=Test Page 817876|status=tagged|notes= [[special:diff/783498|AI tagged]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
mixn33s4uu36a6735ga7940t0souqfo
783501
783500
2026-11-08T13:54:48Z
DVRTed
55993
Undid revision [[Special:Diff/783500|783500]] by [[Special:Contributions/DVRTed|DVRTed]] ([[User talk:DVRTed|talk]])
783501
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=test|status=unnecessary|notes=customnotes}}
{{AIC article row|article=Bob Morley|status=completed|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=foo {{slink}}}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=completed|notes=6 edits: [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=bar {{slink}}}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
286h7a1hfwcqdf6ptzfm5wyo3c9e7k8
783504
783501
2026-11-08T14:14:26Z
DVRTed
55993
Rebuilding tracking table: retain statuses, delete all missing entries (using [[User:DVRTed/AINB-helper|AINB-helper]])
783504
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=test|status=unnecessary|notes=customnotes}}
{{AIC article row|article=Bob Morley|status=completed|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=foo {{slink}}}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Bob Morley|status=requested|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=6 edits: [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
pw25zyos8dfg37m6i2xtl677wad32ht
783505
783504
2026-11-08T14:15:13Z
DVRTed
55993
Rebuilding tracking table: retain statuses, delete all missing entries (using [[User:DVRTed/AINB-helper|AINB-helper]])
783505
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=test|status=unnecessary|notes=customnotes}}
{{AIC article row|article=Bob Morley|status=completed|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=foo {{slink}}}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
hf731uwzklg71dcd27cqti7vasn8vgs
783506
783505
2026-11-08T14:16:22Z
DVRTed
55993
Rebuilding tracking table: retain statuses, delete missing to-dos (using [[User:DVRTed/AINB-helper|AINB-helper]])
783506
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Bob Morley|status=completed|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=6 edits: [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
{{AIC article row|article=test|status=unnecessary|notes=customnotes}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
sptemu17qgc40ai2shgcjhqh576r2mi
783566
783506
2026-11-08T20:48:21Z
DVRTed
55993
Updated status for [[Bob Morley]] to tagged (using [[User:DVRTed/AINB-helper|AINB-helper]])
783566
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-05}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Bob Morley|status=tagged|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=6 edits: [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
{{AIC article row|article=test|status=unnecessary|notes=customnotes}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
55d5bdqtrxkwoj61t90riqpps46qtyq
783567
783566
2026-11-08T20:48:41Z
DVRTed
55993
Adding tracking table (using [[User:DVRTed/AINB-helper|AINB-helper]])
783567
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Test Page 817876|status=requested|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=requested|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
p4p5hiitvqqww743tcx8hs4hp0bjinh
783568
783567
2026-11-08T20:48:50Z
DVRTed
55993
Updated status for [[Test Page 817876]] to completed (using [[User:DVRTed/AINB-helper|AINB-helper]])
783568
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=requested|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
fc4cmddf4eonnx8mrezw30a5xqg4jeo
783571
783568
2026-11-08T20:49:04Z
DVRTed
55993
Updated status for [[Bob Morley]] to ongoing (using [[User:DVRTed/AINB-helper|AINB-helper]])
783571
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
iev0j8ut1yd4vzh63kfk2w8ndedcqj7
783572
783571
2026-11-08T20:50:40Z
DVRTed
55993
Reindexed 1 tracking table(s) (using [[User:DVRTed/AINB-helper|AINB-helper]])
783572
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
pyw865y2mpregapgc3ehzuj5iyrdx9z
783573
783572
2026-11-08T20:51:11Z
DVRTed
55993
/* Tracking list */
783573
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
sxp7b1ujgt8kbftg98k4h8vidzh233z
783574
783573
2026-11-08T20:51:17Z
DVRTed
55993
Updated status for [[Test Page 817876]] to ongoing (using [[User:DVRTed/AINB-helper|AINB-helper]])
783574
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
nhxtx0scgnsycw7itary86fa9d8d0k5
783575
783574
2026-11-08T20:51:39Z
DVRTed
55993
783575
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
pyw865y2mpregapgc3ehzuj5iyrdx9z
783576
783575
2026-11-08T20:52:26Z
DVRTed
55993
783576
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
awegelhpyx5lyk9aj0y1ri60mhsrefs
783577
783576
2026-11-08T20:52:49Z
DVRTed
55993
783577
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=one }}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 twp}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
9grf16ant1ixjkaze7a9vlxb0m7bf6x
783578
783577
2026-11-08T20:53:00Z
DVRTed
55993
Updated status for [[Test Page 817876]] to tagged (using [[User:DVRTed/AINB-helper|AINB-helper]])
783578
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=tagged|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Test Page 817876|status=tagged|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Test Page 817876|status=tagged|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
ns2o86960jrfagegrmzr3k2ttc5fjec
783579
783578
2026-11-08T20:53:16Z
DVRTed
55993
Updated status for [[Test Page 817876]] to completed (using [[User:DVRTed/AINB-helper|AINB-helper]])
783579
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
awegelhpyx5lyk9aj0y1ri60mhsrefs
783580
783579
2026-11-08T20:53:33Z
DVRTed
55993
([[:m:User:Max/MaxEdit.js|MaxEdit]])
783580
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=one }}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 twp}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
9grf16ant1ixjkaze7a9vlxb0m7bf6x
783583
783580
2026-11-08T20:53:47Z
DVRTed
55993
Updated status for [[Test Page 817876]] to ongoing (using [[User:DVRTed/AINB-helper|AINB-helper]])
783583
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=ongoing|notes=one [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 twp [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
fgphz67n5gndiiusgse8mvcfi5o548o
783584
783583
2026-11-08T20:54:53Z
DVRTed
55993
/* Tracking list */
783584
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=invalid |
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=ongoing|notes=one [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 twp [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
2x1s7vbh0dehbbgp6dvz8tnw3crzq1g
783585
783584
2026-11-08T20:55:00Z
DVRTed
55993
Updated status for [[Test Page 817876]] to completed (using [[User:DVRTed/AINB-helper|AINB-helper]])
783585
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=invalid |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
dqe0ur020avvi0g35l05q80urbelse7
783586
783585
2026-11-08T20:56:07Z
DVRTed
55993
Updated status for [[Test Page 817876]] to ongoing (using [[User:DVRTed/AINB-helper|AINB-helper]])
783586
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=invalid |
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
q7pdv3v8tm0lxux46cvx2kpszcwexc1
783587
783586
2026-11-08T21:00:50Z
DVRTed
55993
Updated status for [[Test Page 817876]] to completed (using [[User:DVRTed/AINB-helper|AINB-helper]])
783587
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=invalid |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
dqe0ur020avvi0g35l05q80urbelse7
783588
783587
2026-11-08T21:00:59Z
DVRTed
55993
Reindexed 1 tracking table(s) (using [[User:DVRTed/AINB-helper|AINB-helper]])
783588
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
iivc7qy3mfdmwgezg9lh4iu8ta0ha4w
783589
783588
2026-11-08T21:01:06Z
DVRTed
55993
Updated status for [[Test Page 817876]] to unnecessary (using [[User:DVRTed/AINB-helper|AINB-helper]])
783589
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
jsules42657vyn8a5p7jpdz12zvp5wb
783590
783589
2026-11-08T21:01:18Z
DVRTed
55993
Adding tracking table (using [[User:DVRTed/AINB-helper|AINB-helper]])
783590
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Test Page 817876|status=requested|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=requested|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
g8rrc6neonssl1sz82we9ip2be92ghd
783591
783590
2026-11-08T21:01:26Z
DVRTed
55993
Reindexed 2 tracking table(s) (using [[User:DVRTed/AINB-helper|AINB-helper]])
783591
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=requested|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=requested|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
7rwajbvtenxs5g1x6fxnag808pd8xf9
783592
783591
2026-11-08T21:01:36Z
DVRTed
55993
Updated status for [[Test Page 817876]] to completed (using [[User:DVRTed/AINB-helper|AINB-helper]])
783592
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=completed|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=requested|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
bwqcn9qgbd45pholj9ckhfhhz1i8qkv
783593
783592
2026-11-08T21:04:59Z
DVRTed
55993
Updated status for [[Test Page 817876]] to ongoing (using [[User:DVRTed/AINB-helper|AINB-helper]])
783593
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=ongoing|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=requested|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
g92qr6lahhksb40mk6iou94s98xep4w
783594
783593
2026-11-08T21:05:26Z
DVRTed
55993
([[:m:User:Max/MaxEdit.js|MaxEdit]])
783594
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=invalid |
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=ongoing|notes=one [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 twp [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
2x1s7vbh0dehbbgp6dvz8tnw3crzq1g
783595
783594
2026-11-08T21:05:40Z
DVRTed
55993
Updated status for [[Test Page 817876]] to completed (using [[User:DVRTed/AINB-helper|AINB-helper]])
783595
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=invalid |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
dqe0ur020avvi0g35l05q80urbelse7
783596
783595
2026-11-08T21:07:50Z
DVRTed
55993
Updated status for [[Test Page 817876]] to unnecessary (using [[User:DVRTed/AINB-helper|AINB-helper]])
783596
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=invalid |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
6r1l8vqgl1xtjb4wnu5t9ab4ony3y9k
783597
783596
2026-11-08T21:11:04Z
DVRTed
55993
Updated status for [[Test Page 817876]] to ongoing (using [[User:DVRTed/AINB-helper|AINB-helper]])
783597
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=invalid |
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=ongoing|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
q7pdv3v8tm0lxux46cvx2kpszcwexc1
783598
783597
2026-11-08T21:12:06Z
DVRTed
55993
Updated status for [[Test Page 817876]] to unnecessary (using [[User:DVRTed/AINB-helper|AINB-helper]])
783598
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=invalid |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
6r1l8vqgl1xtjb4wnu5t9ab4ony3y9k
783599
783598
2026-11-08T21:12:20Z
DVRTed
55993
/* Tracking list */
783599
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=21 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=requested|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
3ybry8sas63o1r1ny4dld1lzts02req
783600
783599
2026-11-08T21:12:26Z
DVRTed
55993
Updated status for [[Testpage12]] to completed (using [[User:DVRTed/AINB-helper|AINB-helper]])
783600
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=21 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=requested|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
00yahni9dmrnf29xg6uvusfxa9ruxe7
783601
783600
2026-11-08T21:12:31Z
DVRTed
55993
Updated status for [[BS Nymph]] to tagged (using [[User:DVRTed/AINB-helper|AINB-helper]])
783601
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=21 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
j0opkqqe96iyrysodv22j7x6zz0m969
783602
783601
2026-11-08T21:12:43Z
DVRTed
55993
Adding tracking table (using [[User:DVRTed/AINB-helper|AINB-helper]])
783602
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=21 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Test Page 817876|status=requested|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=requested|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
94rp9qna7q0m15idyno9ib4i97704b8
783603
783602
2026-11-08T21:12:59Z
DVRTed
55993
Reindexed 2 tracking table(s) (using [[User:DVRTed/AINB-helper|AINB-helper]])
783603
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=requested|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=requested|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
nv5sy4vhccl76v3igi5b8rxe0f3lxvr
783604
783603
2026-11-08T21:13:10Z
DVRTed
55993
Updated status for [[Bob Morley]] to ongoing (using [[User:DVRTed/AINB-helper|AINB-helper]])
783604
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=requested|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
bwg9n1nxc00dxqag209ecwj7xlne43q
783605
783604
2026-11-08T21:13:21Z
DVRTed
55993
Updated status for [[Bob Morley]] to completed (using [[User:DVRTed/AINB-helper|AINB-helper]])
783605
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=requested|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley|status=completed|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
qxh78a208dhqgjzumrkfizqvj8iq4vd
783606
783605
2026-11-08T21:14:03Z
DVRTed
55993
/* Cleanup */
783606
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |
{{AIC article row|article=Test Page 817876|status=requested|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley2|status=completed|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
jvjp6m13oukfssiibe66yykbkrz5nhu
783607
783606
2026-11-08T21:14:11Z
DVRTed
55993
Updated status for [[Bob Morley2]] to tagged (using [[User:DVRTed/AINB-helper|AINB-helper]])
783607
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |
{{AIC article row|article=Test Page 817876|status=requested|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley2|status=tagged|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
4efilgwyy99t86jo04fqvnxkpscgjpk
783608
783607
2026-11-08T21:16:08Z
DVRTed
55993
Reindexed 2 tracking table(s) (using [[User:DVRTed/AINB-helper|AINB-helper]])
783608
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=requested|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley2|status=tagged|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
69aiqe3501z4szd2gis4kfesynyaqf6
783609
783608
2026-11-08T21:16:18Z
DVRTed
55993
Updated status for [[Test Page 817876]] to completed (using [[User:DVRTed/AINB-helper|AINB-helper]])
783609
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=unnecessary|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=completed|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley2|status=tagged|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
i8rtenth90e836h3qlaw5agg4vdaahw
783610
783609
2026-11-08T21:16:31Z
DVRTed
55993
Updated status for [[Test Page 817876]] to tagged (using [[User:DVRTed/AINB-helper|AINB-helper]])
783610
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=tagged|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=tagged|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=tagged|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=ongoing|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=completed|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley2|status=tagged|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
3htwaniwzis6fgqyyeyhghij6590xqc
783611
783610
2026-11-08T21:23:28Z
DVRTed
55993
Batch edited 2 rows: 2 completed (using [[User:DVRTed/AINB-helper|AINB-helper]])
783611
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=completed|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=completed|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=completed|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley2|status=tagged|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
4mwvwmpcr5ma87zk47ms3am7e1b9yfx
783612
783611
2026-11-08T21:23:34Z
DVRTed
55993
Updated status for [[Test Page 817876]] to tagged (using [[User:DVRTed/AINB-helper|AINB-helper]])
783612
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=1 |
{{AIC article row|article=Test Page 817876|status=tagged|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=tagged|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Test Page 817876|status=tagged|notes=11 edits: [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]] [[special:diff/783581|prodded]]}}
{{AIC article row|article=Bob Morley|status=completed|notes=2 edits: [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]] [[special:diff/783569|prodded]]}}
{{AIC article row|article=Testpage12|status=completed|notes=27 edits: [[Special:Diff/781761|(-2584)]] [[Special:Diff/765089|(+330)]] [[Special:Diff/764111|(+72)]] [[Special:Diff/762674|(+88)]] [[Special:Diff/762672|(+318)]] [[Special:Diff/762671|(+88)]] [[Special:Diff/762670|(+318)]] [[Special:Diff/762610|(+88)]] [[Special:Diff/762607|(+318)]] [[Special:Diff/762606|(+318)]] [[Special:Diff/762604|(+318)]] [[Special:Diff/762424|(+320)]] [[Special:Diff/762423|(-515)]] [[Special:Diff/762237|(+323)]] [[Special:Diff/762235|(+96)]] [[Special:Diff/762233|(+96)]] [[Special:Diff/762232|(-418)]] [[Special:Diff/762229|(+96)]] [[Special:Diff/762227|(+323)]] [[Special:Diff/762226|(-323)]] [[Special:Diff/762223|(+323)]] [[Special:Diff/762222|(-442)]] [[Special:Diff/762220|(+5)]] [[Special:Diff/762217|(+103)]] [[Special:Diff/762215|(+335)]] [[Special:Diff/761537|(+2)]] [[Special:Diff/761536|(+4)]] (page created)}}
{{AIC article row|article=BS Nymph|status=tagged|notes=24 edits: [[Special:Diff/768763|(+70)]] [[Special:Diff/768500|(+314)]] [[Special:Diff/765391|(+5)]] [[Special:Diff/762815|(+330)]] [[Special:Diff/762814|(-2679)]] [[Special:Diff/762811|(+88)]] [[Special:Diff/762776|(+331)]] [[Special:Diff/762774|(+90)]] [[Special:Diff/762772|(+283)]] [[Special:Diff/762716|(+88)]] [[Special:Diff/762713|(+88)]] [[Special:Diff/762710|(+88)]] [[Special:Diff/762707|(+318)]] [[Special:Diff/762691|(+318)]] [[Special:Diff/762688|(+88)]] [[Special:Diff/762686|(+318)]] [[Special:Diff/762684|(+88)]] [[Special:Diff/762682|(+88)]] [[Special:Diff/762681|(+88)]] [[Special:Diff/762678|(+318)]] [[Special:Diff/762677|(-88)]] [[Special:Diff/762676|(+88)]] [[Special:Diff/759773|(-85)]] [[Special:Diff/759772|(+85)]]}}
{{AIC article row|article=Wp/sas/Kabupatén Tangerang|status=requested|notes=5 edits: [[Special:Diff/763465|(+330)]] [[Special:Diff/763463|(+88)]] [[Special:Diff/762818|(+88)]] [[Special:Diff/759771|(-317)]] [[Special:Diff/759770|(+317)]]}}
{{AIC article row|article=Jugerum/edithistory|status=requested|notes=2 edits: [[Special:Diff/760823|(-343)]] [[Special:Diff/760822|(+343)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=completed|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley2|status=tagged|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
ktfco5h70ps050xprv9qzyw1z9yxdfw
783613
783612
2026-11-08T21:23:57Z
DVRTed
55993
Rebuilding tracking table: retain statuses, delete all missing entries (using [[User:DVRTed/AINB-helper|AINB-helper]])
783613
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Bob Morley|status=completed|notes=1 edit: [[Special:Diff/782418|(+314)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=completed|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley2|status=tagged|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
71205nbgxfale5m47r46gz2gvqksvup
783644
783613
2026-11-09T00:13:47Z
DVRTed
55993
783644
wikitext
text/x-wiki
{{AINB case banner|cleanup=analysis|llmprod=undetermined|conduct=review|case_type=article}}
== Cleanup ==
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
test
=== Tracking list ===
{{AIC article list|
{{AIC article row|article=Bob Morley|status=completed|notes=1 edit: [[Special:Diff/782418|(+314)]]}}
}}
{{InfoCard|content='''Tracker detail'''
* User: {{User-ainb|1=DVRTed}}
* Start date: 2022-12-01
* End date: 2026-11-08}}
=== Tracking list ===
{{AIC article list |index=2 |
{{AIC article row|article=Test Page 817876|status=completed|notes=12 edits: [[Special:Diff/783581|(+314)]] [[Special:Diff/783498|(+71)]] [[Special:Diff/783494|(+71)]] [[Special:Diff/783488|(-142)]] [[Special:Diff/783485|(+142)]] [[Special:Diff/783484|(-1322)]] [[Special:Diff/782062|(+314)]] [[Special:Diff/782058|(+314)]] [[Special:Diff/781260|(+325)]] [[Special:Diff/780857|(+314)]] [[Special:Diff/765241|(+83)]] [[Special:Diff/691118|(+14)]]}}
{{AIC article row|article=Bob Morley2|status=tagged|notes=3 edits: [[Special:Diff/783569|(+314)]] [[Special:Diff/782418|(+314)]] [[Special:Diff/762028|(+342)]]}}
}}
4gkpk6irex5i7ah1z4yqtl27g07hfz3
Test Page 817876
0
167164
783484
782062
2026-11-08T13:37:48Z
DVRTed
55993
Replaced content with " Hi I'm a new page == test 1== foo baaaaaar ==test2== yay!"
783484
wikitext
text/x-wiki
Hi I'm a new page
== test 1==
foo baaaaaar
==test2==
yay!
ekf4bjqdo6x5zpcribglbh2bgbd3l40
783485
783484
2026-11-08T13:40:08Z
DVRTed
55993
Added AI tag, see [[User:DVRTed/sandbox2]] (using [[User:DVRTed/AINB-helper|AINB-helper]])
783485
wikitext
text/x-wiki
Hi I'm a new page
== test 1==
{{AI-generated |reason= [[User:DVRTed/sandbox2]] |date=November 2026}}
foo baaaaaar
==test2==
{{AI-generated |reason= [[User:DVRTed/sandbox2]] |date=November 2026}}
yay!
03ae27et99f0zpdz9o1fibazqsmjfat
783488
783485
2026-11-08T13:40:25Z
DVRTed
55993
Undid revision [[Special:Diff/783485|783485]] by [[Special:Contributions/DVRTed|DVRTed]] ([[User talk:DVRTed|talk]])
783488
wikitext
text/x-wiki
Hi I'm a new page
== test 1==
foo baaaaaar
==test2==
yay!
ekf4bjqdo6x5zpcribglbh2bgbd3l40
783494
783488
2026-11-08T13:51:39Z
DVRTed
55993
Added AI tag, see [[User:DVRTed/sandbox2]] (using [[User:DVRTed/AINB-helper|AINB-helper]])
783494
wikitext
text/x-wiki
{{AI-generated |reason= [[User:DVRTed/sandbox2]] |date=November 2026}}
Hi I'm a new page
== test 1==
foo baaaaaar
==test2==
yay!
frs1vj2tsa9t7r0bdfh8e1ywcmnm65a
783498
783494
2026-11-08T13:53:06Z
DVRTed
55993
Added AI tag, see [[User:DVRTed/sandbox2]] (using [[User:DVRTed/AINB-helper|AINB-helper]])
783498
wikitext
text/x-wiki
{{AI-generated |reason= [[User:DVRTed/sandbox2]] |date=November 2026}}
{{AI-generated |reason= [[User:DVRTed/sandbox2]] |date=November 2026}}
Hi I'm a new page
== test 1==
foo baaaaaar
==test2==
yay!
6gfz1rqhq5uwl6udnsk8kkkomvx0r5t
783581
783498
2026-11-08T20:53:45Z
DVRTed
55993
[[WP:LLMPRV|Presumptive removal of LLM-generated content]], see [[User:DVRTed/sandbox2]]. Please do not remove this tag without following [[WP:LLMPRVOBJ|the procedures for disputing presumptive removal of LLM-generated content]]. (using [[User:DVRTed/AINB-helper|AINB-helper]])
783581
wikitext
text/x-wiki
{{Prod llm/dated
| concern = [[WP:LLMPRV|Presumptive removal of LLM-generated content]], see [[User:DVRTed/sandbox2]]. Please do not remove this tag without following [[WP:LLMPRVOBJ|the procedures for disputing presumptive removal of LLM-generated content]].
| user =
| timestamp = 20261108205345
| help = off
}}
{{AI-generated |reason= [[User:DVRTed/sandbox2]] |date=November 2026}}
{{AI-generated |reason= [[User:DVRTed/sandbox2]] |date=November 2026}}
Hi I'm a new page
== test 1==
foo baaaaaar
==test2==
yay!
d56mc0tzr3cif7wmyduytcsapdia97m
User:Nardog/sandbox9.js
2
171161
783558
769612
2026-11-08T18:37:49Z
Nardog
40946
783558
javascript
text/javascript
window.iac?.toggle(false);
$('.insertanychar, .tool[rel="insertanychar"]').remove();
$('head > style').filter(function () {
return this.textContent.includes('.insertanychar');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(function () {
let clicked, iac;
class Iac {
constructor(context) {
this.context = context;
this.history = new OO.ui.SelectWidget({
classes: ['insertanychar-history']
}).on('choose', item => {
this.history.unselectItem(item);
this.insert(item.getData());
});
this.historyToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'history',
invisibleLabel: true,
label: 'History',
value: true
}).toggle().connect(this.history, { change: 'toggle' });
this.chars = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.chars.select.unselectItem(item);
if (this.chars.loaders.includes(item)) {
this.renderChars(item);
return;
}
this.insert(item.getData());
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter characters',
value: true
}).connect(this, { change: ['onFilterToggle', 'chars'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter characters'
}).on('change', OO.ui.debounce(this.filterChars, 500)),
filterMethod: 'filterChars',
prevBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'previous',
invisibleLabel: true,
label: 'Previous block'
}).toggle().connect(this, { click: ['goToBlock', true] }),
$blockLink: $('<a>').attr({
class: 'external',
rel: 'nofollow',
title: 'Open Unicode PDF',
target: '_blank'
}),
$info: $('<span>').addClass('insertanychar-info'),
nextBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'next',
invisibleLabel: true,
label: 'Next block'
}).toggle().connect(this, { click: 'goToBlock' }),
reset: new OO.ui.ButtonWidget({
flags: ['destructive'],
framed: false,
label: 'Reset',
title: 'Show all characters'
}).toggle().connect(this, { click: 'resetBlock' })
};
this.compactToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'viewCompact',
invisibleLabel: true,
label: 'Compact view'
}).on('change', on => {
let items = this.chars.select.getItems();
let firstVisible = this.getFirstVisible('chars', items);
this.chars.select.$element.toggleClass('insertanychar-compact', on);
let lang = this.options.lang.getValue();
items.forEach(item => {
if (!this.chars.loaders.includes(item)) {
this.renderChar(item, on, lang);
}
});
this.scroll('chars', firstVisible, {
alignToTop: true,
animate: false
});
});
this.chars.$notice = $('<div>').addClass('insertanychar-notice').append(
this.chars.prevBlock.$element,
$('<span>').addClass('insertanychar-name').append(
this.chars.$blockLink,
this.chars.$info
),
this.chars.nextBlock.$element,
this.chars.reset.$element
);
this.chars.tab = new OO.ui.TabPanelLayout('chars', {
$content: $([
this.chars.$notice[0],
this.chars.select.$element[0]
]),
label: 'Characters'
}).connect(this.chars.filterToggle, { active: 'toggle' })
.connect(this.chars.filter, { active: 'toggle' })
.connect(this.compactToggle, { active: 'toggle' });
this.blocks = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.blocks.select.unselectItem(item);
this.showBlock(item.getData());
this.index.setTabPanel('chars');
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter blocks',
value: true
}).toggle().connect(this, { change: ['onFilterToggle', 'blocks'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter blocks'
}).toggle().on('change', OO.ui.debounce(this.filterBlocks, 250)),
filterMethod: 'filterBlocks',
$notice: $('<div>').addClass('insertanychar-notice insertanychar-info')
};
this.blocks.tab = new OO.ui.TabPanelLayout('blocks', {
$content: $([
this.blocks.$notice[0],
this.blocks.select.$element[0]
]),
label: 'Blocks'
}).connect(this.blocks.filterToggle, { active: 'toggle' })
.connect(this.blocks.filter, { active: 'toggle' });
this.index = new OO.ui.IndexLayout({
autoFocus: false,
framed: false
}).addTabPanels([this.chars.tab, this.blocks.tab]).on('set', tab => {
let side = tab.getName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
});
this.options = {
font: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: 0,
label: 'Serif first'
}),
new OO.ui.MenuOptionWidget({
data: 1,
label: 'Sans-serif first'
})
]
}
}),
lang: new OO.ui.ComboBoxInputWidget({
$overlay: true,
options: [
{ data: '', label: '(Default)' },
{ data: 'ja' },
{ data: 'ko' },
{ data: 'vi' },
{ data: 'zh-Hans' },
{ data: 'zh-Hant' },
{ data: 'zh-Hant-HK' },
{ data: 'zh-Hant-TW' }
],
placeholder: '(Default)'
}).on('change', on => {
this.$pane.find('.insertanychar-char').attr('lang', on || null);
}),
vs: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: '',
label: 'None'
})
]
}
})
};
this.options.font.getMenu().selectItemByData(0).on('choose', item => {
this.$pane.toggleClass('insertanychar-sans', !!item.getData());
});
this.options.vs.getMenu().selectItemByData('').on('choose', item => {
let v = item.getData();
this.$pane.css(
'--insertanychar-vs',
v ? `'\\${v.toString(16)}'` : ''
);
});
this.options.panel = new OO.ui.PanelLayout({
$content: $([
new OO.ui.FieldLayout(this.options.font, {
label: 'Font stack:'
}).$element[0],
new OO.ui.FieldLayout(this.options.lang, {
label: 'Language:'
}).$element[0],
new OO.ui.FieldLayout(this.options.vs, {
label: 'Variation selector:'
}).$element[0]
]),
expanded: false,
padded: true
}).toggle();
this.optionsToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'textStyle',
invisibleLabel: true,
label: 'Preview options'
}).connect(this.options.panel, { change: 'toggle' });
this.chars.loaders = [
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
}),
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
})
];
this.chars.observer = new IntersectionObserver(entries => {
if (!entries[0].isIntersecting) return;
this.renderChars(this.chars.loaders.find(loader => (
loader.$element.is(entries[0].target))
));
}, { threshold: 0.75 });
this.chars.loaders.forEach(loader => {
this.chars.observer.observe(loader.$element[0]);
});
this.twoPaneLayout = new mw.loader.moduleRegistry['ext.wikiEditor.realtimepreview']
.packageExports['realtimepreview/TwoPaneLayout.js']();
this.$pane = this.twoPaneLayout.getPane2()
.addClass('insertanychar insertanychar-loading')
.text('Loading...');
}
ready(blocks) {
this.chars.count = 0;
this.blocks.blocks = blocks;
this.blocks.count = blocks.length;
let vs = [];
this.blocks.items = blocks.map((block, i) => {
if (block.name.startsWith('Variation Selectors')) {
vs.push(...block.entries);
}
let { sample } = block;
delete block.sample;
let $sample = $('<span>').addClass('insertanychar-char');
if (sample) {
$sample.text(Iac.renderChar(sample.v, sample.combining));
}
block.count = Iac.isRange(block.entries[0])
? block.entries[0].end - block.entries[0].start
: block.entries.length;
this.chars.count += block.count;
return new OO.ui.MenuOptionWidget({
data: block,
disabled: !block.count,
label: $([
$sample[0],
$('<span>').addClass('insertanychar-name').text(
block.name
).append(
$('<span>').addClass('insertanychar-info').text(
`${block.count.toLocaleString('en')} characters`
)
)[0],
$('<span>').addClass('insertanychar-num').text(i + 1)[0]
])
});
});
this.blocks.select.addItems(this.blocks.items);
this.options.vs.getMenu().addItems(vs.map(entry => (
new OO.ui.MenuOptionWidget({
data: entry.v,
label: `${entry.name.slice(19)} (${Iac.getHex(entry.v)})`
})
)));
this.blocks.$notice
.text(`${this.blocks.count.toLocaleString('en')} blocks`);
this.$pane.empty().removeClass('insertanychar-loading').append(
this.history.$element,
this.index.$element,
this.options.panel.$element,
$('<div>').addClass('insertanychar-bar').append(
this.historyToggle.$element,
this.chars.filterToggle.$element,
this.chars.filter.$element,
this.blocks.filterToggle.$element,
this.blocks.filter.$element,
this.optionsToggle.$element,
this.compactToggle.$element
)
).on('keydown', e => {
if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
let isEnter = e.which === 13;
let isUp = e.which === 38;
let isDown = e.which === 40;
let isHome = e.which === 36;
let isEnd = e.which === 35;
let isPgUp = e.which === 33;
let isPgDown = e.which === 34;
if (!(isEnter || isUp || isDown || isHome || isEnd || isPgUp || isPgDown)) return;
e.preventDefault();
let side = this.index.getCurrentTabPanelName();
if (isEnter) {
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
this[side].select.chooseItem(highlighted);
}
return;
}
let item = (isUp || isDown) && this.getAdjacent(side, isUp) ||
(isPgUp || isPgDown) && this.getPageAway(side, isPgUp) ||
this.getEdge(side, isDown || isPgUp || isHome);
if (item) {
this[side].select.highlightItem(item);
this.scroll(side, item);
}
});
this.filterChars(true);
if (!OO.ui.isMobile()) {
this.chars.filter.focus();
}
}
toggle(visible = !this.visible) {
let $uiText = this.context.$ui
.toggleClass('insertanychar-enabled', visible)
.find('.wikiEditor-ui-text');
if (visible) {
$uiText.before(this.twoPaneLayout.$element);
this.twoPaneLayout.setPane1($uiText);
this.twoPaneLayout.$element.show();
this.twoPaneLayout.$element.css('height', $uiText.height() + 'px');
$uiText.css('height', '100%');
let side = this.index.getCurrentTabPanelName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
} else {
$uiText.css('height', this.twoPaneLayout.$element.height() + 'px');
this.twoPaneLayout.$element.after($uiText).hide();
}
this.visible = visible;
this.context.$ui.find('.tool[rel="insertanychar"]').data('ooui')
.setFlags({ progressive: visible });
return this;
}
insert(data) {
this.context.$textarea.textSelection('encapsulateSelection', {
peri: String.fromCodePoint(data.v),
selectPeri: false
});
let historyItem = this.history.getItems().find(item => (
item.getData().v === data.v
)) || this.renderChar(data, true);
this.history.addItems([historyItem], 0);
this.historyToggle.toggle(true);
}
filterChars = first => {
let prevSearch = this.chars.search;
this.chars.search = new Search(
this.chars.filter.getValue(),
this.chars.filterToggle.getValue()
);
if (!first && this.block === this.prevBlock &&
this.chars.search.compare(prevSearch)
) {
return;
}
this.prevBlock = this.block;
this.chars.entries = this.block
? this.block.entries
: this.blocks.blocks.flatMap(block => block.entries);
if (!this.chars.search.valid) {
this.chars.$info.text(`${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars(null, !this.block);
return;
}
let matches = [[], [], [], [], [], []];
this.chars.entries.forEach(entry => {
if (!Iac.isRange(entry)) {
let result = this.chars.search.test(entry.name);
if (result) {
matches[result - 1].push(entry);
}
return;
}
if (!entry.isHangul && !this.chars.search.testHex(entry.name)) return;
for (let v = entry.start; v <= entry.end; v++) {
let nameToTest = Iac.getCharName(entry, v, true);
let result = this.chars.search.test(nameToTest);
if (result) {
matches[result - 1].push({
v,
name: entry.isHangul ? nameToTest : entry.name
});
}
}
});
this.chars.entries = matches.flat();
this.chars.$info.text(`${
this.chars.entries.length.toLocaleString('en')
} / ${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars();
};
renderChars(loader, startMid) {
let compact = this.compactToggle.getValue();
let lang = this.options.lang.getValue();
let prevItems = this.chars.select.getItems();
let prevCharItems = prevItems
.filter(item => !this.chars.loaders.includes(item));
let i = 0, blockV;
if (startMid) {
let firstVisible = this.getFirstVisible('chars', prevCharItems);
if (firstVisible) {
let data = firstVisible.getData();
this.chars.entries.some((entry, j) => {
if (Iac.isRange(entry)) {
if (Iac.inBlock(data.v, entry)) {
i = j;
blockV = data.v;
return true;
}
} else if (entry.v === data.v) {
i = j;
return true;
}
});
}
}
let up = loader === this.chars.loaders[0];
let increment = up ? -1 : 1;
if (loader) {
this.chars.select.removeItems(this.chars.loaders);
let adjacent = prevCharItems.at(up ? 0 : -1);
if (adjacent) {
let data = adjacent.getData();
if (Iac.isRange(data.entry)) {
i = this.chars.entries.indexOf(data.entry);
blockV = data.v + increment;
} else {
i = this.chars.entries.indexOf(data.entry) + increment;
}
}
} else {
this.chars.select.removeItems(prevItems);
}
let toAdd = [], len = 0;
do {
let entry = this.chars.entries[i];
if (!entry) break;
if (!Iac.isRange(entry)) {
len = toAdd.push(this.renderChar({
v: entry.v,
entry
}, compact, lang));
i += increment;
continue;
}
if (typeof blockV === 'number') {
if (!Iac.inBlock(blockV, entry)) {
blockV = null;
i += increment;
continue;
}
} else {
blockV = entry[up ? 'end' : 'start'];
}
len = toAdd.push(this.renderChar({
v: blockV,
entry
}, compact, lang));
blockV += increment;
} while (len < 128);
if (loader) {
let toRemove = up
? prevCharItems.slice(384 - len)
: prevCharItems.slice(0, -384 + len);
this.chars.select.removeItems(toRemove);
}
if (up) {
this.chars.select.addItems(toAdd.reverse(), 0);
} else {
this.chars.select.addItems(toAdd);
}
let reached = {};
let items = this.chars.select.getItems();
if (items.length) {
[['start', 0], ['end', -1]].forEach(([pos, idx]) => {
let data = items.at(idx).getData();
reached[pos] = data.entry === this.chars.entries.at(idx) && (
!Iac.isRange(data.entry) || data.v === data.entry[pos]
);
});
} else {
reached.start = true;
reached.end = true;
}
if (!reached.start) {
this.chars.select.addItems([this.chars.loaders[0]], 0);
}
if (!reached.end) {
this.chars.select.addItems([this.chars.loaders[1]]);
}
if (loader) {
this.scroll('chars', toAdd.at(up ? -1 : 0), {
alignToTop: up,
animate: false
});
} else {
this.chars.tab.$element[0].scrollTop = 0;
}
}
renderChar(item, compact, lang = this.options.lang.getValue()) {
let data;
if ($.isPlainObject(item)) {
data = item;
item = new OO.ui.MenuOptionWidget({ data });
data.name = Iac.getCharName(data.entry, data.v).replace(
/ (?:AND|AS|AT|BY|FOR|FROM|IN|OF|ON|OR|THE|TO|WITH)(?= )/g,
s => s.toLowerCase()
).replace(
/\b(?!CJK\b)([A-Z])([A-Z]+)\b/g,
($0, $1, $2) => $1 + $2.toLowerCase()
);
} else {
data = item.getData();
}
let block = this.block ||
this.blocks.blocks.find(b => Iac.inBlock(data.v, b));
let hex = Iac.getHex(data.v);
let $label = item.getLabel() || $('<span>').attr({
class: 'insertanychar-char',
lang: lang
}).text(Iac.renderChar(data.v, data.entry.combining));
if (!compact && !$label.children().length) {
$label = $([
$label[0],
$('<span>').addClass('insertanychar-name').text(
data.name
).append(
!this.block && $('<span>').addClass('insertanychar-block').append(
$('<a>').attr({
href: '#',
title: 'Show only this block'
}).text(block.name)
.on('click', block, this.onBlockClick)
.on('mousedown', Iac.stopPropagation)
)
)[0],
$('<span>').addClass('insertanychar-hex').text(hex)[0]
]);
}
return item.setLabel($label).setTitle(
compact ? `${data.name} (${hex})\n${block.name}` : null
);
}
showBlock(block) {
this.prevBlock = this.block;
this.block = block;
this.chars.prevBlock.toggle(true);
this.chars.$blockLink.attr(
'href',
`https://www.unicode.org/charts/PDF/U${Iac.getHex(block.start)}.pdf`
).text(block.name).show();
this.chars.reset.toggle(true);
this.chars.nextBlock.toggle(true);
if (this.chars.filterToggle.getValue()) {
this.chars.filterToggle.setValue(false);
} else {
this.filterChars();
}
}
goToBlock(prev) {
let i = this.blocks.blocks.indexOf(this.block) + (prev ? -1 : 1);
let block = this.blocks.blocks.at(i) || this.blocks.blocks[0];
this.showBlock(block);
}
resetBlock() {
this.prevBlock = this.block;
this.block = null;
this.chars.prevBlock.toggle(false);
this.chars.$blockLink.hide();
this.chars.nextBlock.toggle(false);
this.chars.reset.toggle(false);
this.filterChars();
}
filterBlocks = () => {
let prevSearch = this.blocks.search;
this.blocks.search = new Search(
this.blocks.filter.getValue(),
this.blocks.filterToggle.getValue()
);
if (this.blocks.search.compare(prevSearch)) return;
let items = this.blocks.select.getItems(), firstVisible;
if (prevSearch?.valid && !this.blocks.search.valid) {
firstVisible = this.getFirstVisible('chars', items);
}
let filtered = this.blocks.search?.valid;
let toShow = this.blocks.items.filter(item => !filtered || (
this.blocks.search.test(item.getData().name.toUpperCase())
));
this.blocks.select.removeItems(items).addItems(toShow);
this.blocks.$notice.text(`${
filtered ? `${toShow.length.toLocaleString('en')} / ` : ''
}${this.blocks.count.toLocaleString('en')} blocks`);
if (toShow.includes(firstVisible)) {
this.scroll('blocks', firstVisible, {
alignToTop: true,
animate: false
});
} else {
this.blocks.tab.$element[0].scrollTop = 0;
}
};
getFirstVisible(side, items) {
let y = this[side].tab.$element[0].scrollTop;
return items.find(item => item.$element[0].offsetTop >= y);
}
getEdge(side, up) {
return this[side].select.getItems().at(up ? 0 : -1);
}
getAdjacent(side, up) {
let items = this[side].select.getItems();
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
return items[items.indexOf(highlighted) + (up ? -1 : 1)];
}
let y = this[side].tab.$element[0].scrollTop;
if (up) {
return items.findLast(item => item.$element[0].offsetTop < y);
}
y += this[side].tab.$element[0].clientHeight;
return items.find(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight > y
));
}
getPageAway(side, up) {
let items = this[side].select.getItems();
let y = this[side].tab.$element[0].scrollTop +
this[side].tab.$element[0].clientHeight * (up ? -1 : 2);
if (up) {
return items.find(item => item.$element[0].offsetTop >= y);
}
return items.findLast(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight < y
));
}
scroll(side, item, options = {}) {
if (!item) return;
options.padding = { top: this[side].$notice[0].offsetHeight };
item.scrollElementIntoView(options);
}
onFilterToggle(side, on) {
this[this[side].filterMethod]();
if (on && !OO.ui.isMobile()) {
setTimeout(() => {
this[side].filter.focus();
});
}
}
onBlockClick = e => {
e.preventDefault();
this.showBlock(e.data);
};
static stopPropagation(e) {
e.stopPropagation();
}
static getHex(v) {
return v.toString(16).toUpperCase().padStart(4, '0');
}
// https://www.unicode.org/versions/latest/core-spec/chapter-3/
static hangul = {
leads: [
'G', 'GG', 'N', 'D', 'DD', 'R', 'M', 'B', 'BB', 'S', 'SS', '',
'J', 'JJ', 'C', 'K', 'T', 'P', 'H'
],
vowels: [
'A', 'AE', 'YA', 'YAE', 'EO', 'E', 'YEO', 'YE', 'O', 'WA',
'WAE', 'OE', 'YO', 'U', 'WEO', 'WE', 'WI', 'YU', 'EU', 'YI', 'I'
],
trails: [
'', 'G', 'GG', 'GS', 'N', 'NJ', 'NH', 'D', 'L', 'LG', 'LM',
'LB', 'LS', 'LT', 'LP', 'LH', 'M', 'B', 'BS', 'S', 'SS', 'NG',
'J', 'C', 'K', 'T', 'P', 'H'
]
};
static getCharName(entry, v, appendHex) {
if (entry.isHangul) {
let i = v - entry.start;
return entry.name + ' ' + Iac.hangul.leads[Math.floor(i / 588)] +
Iac.hangul.vowels[Math.floor((i % 588) / 28)] +
Iac.hangul.trails[i % 28];
}
if (appendHex) {
return entry.name + ' ' + Iac.getHex(v);
}
return entry.name;
}
static renderChar(v, combining) {
return `${combining ? '◌' : ''}${
String.fromCodePoint(v)
}${combining === 2 ? '◌' : ''}`;
}
static inBlock(v, block) {
return v >= block.start && v <= block.end;
}
static isRange(entry) {
return !!entry && Object.hasOwn(entry, 'start');
}
}
class Search {
constructor(query, valid) {
let terms = [], negTerms = new Set();
if (valid) {
query.toUpperCase().replace(/[^-\s\dA-Z]/g, '').split(/\s+/).forEach(s => {
if (s.startsWith('-')) {
if (s !== '-') {
negTerms.add(s.slice(1));
}
} else {
terms.push(...s.split('-').filter(Boolean));
}
});
}
this.negTerms = [...negTerms].sort();
this.terms = terms.filter(t => (
!this.negTerms.some(nt => t.includes(nt))
)).sort();
this.termCount = terms.length;
this.valid = !!(this.termCount || this.negTerms.length);
}
test(s) {
if (this.negTerms.some(term => s.includes(term))) return;
let words = s.split(/[ -]/);
let exactCount = 0, initialCount = 0, partialCount = 0;
this.terms.filter(term => {
let i = words.indexOf(term);
if (i === -1) {
return true;
}
words.splice(i, 1);
exactCount++;
}).filter(term => {
let i = words.findIndex(word => word.startsWith(term));
if (i === -1) {
return true;
}
words.splice(i, 1);
initialCount++;
}).forEach(term => {
let i = words.findIndex(word => word.includes(term));
if (i === -1) return;
words.splice(i, 1);
partialCount++;
});
switch (this.termCount) {
case exactCount:
return 1;
case exactCount && initialCount && exactCount + initialCount + partialCount:
return 2;
case exactCount && exactCount + partialCount:
return 3;
case initialCount:
return 4;
case initialCount && initialCount + partialCount:
return 5;
case partialCount:
return 6;
}
}
testHex(s) {
return !this.negTerms.some(term => s.includes(term)) &&
this.terms.every(term => s.includes(term) || /^[\dA-F]{1,6}$/.test(term));
}
compare(search) {
if (!search) {
return !this.valid;
}
return search.valid === this.valid &&
String(search.terms) === String(this.terms) &&
String(search.negTerms) === String(this.negTerms);
}
}
let css = mw.loader.addStyleTag(`.wikiEditor-ui:not(.insertanychar-available) .tool[rel="insertanychar"],
.wikiEditor-ui.insertanychar-enabled .tool[rel="realtimepreview"] {
display: none;
}`);
let init = async context => {
if (clicked) {
if (iac) {
iac.context = context;
iac.toggle();
}
return;
}
clicked = true;
css.textContent += `
.insertanychar-loading {
padding: 8px !important;
}
.insertanychar-bar {
display: flex;
}
.insertanychar-bar > .oo-ui-widget {
margin: 0 !important;
}
.insertanychar-history {
display: flex;
overflow: auto;
}
.insertanychar-history > .oo-ui-menuOptionWidget {
padding: 0;
margin: 2px;
}
.insertanychar-history > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label,
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
overflow: visible;
}
.insertanychar > .oo-ui-indexLayout {
flex-grow: 1;
position: relative;
}
.insertanychar .oo-ui-tabSelectWidget {
display: flex;
justify-content: space-evenly;
}
.insertanychar .oo-ui-tabOptionWidget {
margin-inline: 0;
width: 45%;
text-align: center;
}
.insertanychar .oo-ui-tabPanelLayout {
padding: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget {
padding-inline: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
display: flex;
align-items: center;
}
.insertanychar-char {
display: inline-block;
font-size: 200%;
min-width: 1.5em;
min-height: 1.5em;
line-height: 1.5;
text-align: center;
white-space: pre;
font-family: 'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif,'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif;
}
.insertanychar-sans .insertanychar-char {
font-family: 'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif,'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif;
}
.insertanychar-char::after {
content: var(--insertanychar-vs);
}
.insertanychar-history .insertanychar-char {
font-size: 150%;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-char {
margin: 0 2px;
}
.insertanychar-name {
flex-grow: 1;
white-space: normal !important;
word-break: break-word;
}
.insertanychar-block,
.insertanychar-info {
display: block;
}
.insertanychar-block > a,
.insertanychar-block > a:visited,
.insertanychar-info,
.insertanychar-hex,
.insertanychar-num {
font-size: 85%;
color: var(--color-subtle, #54595d);
}
.insertanychar-hex,
.insertanychar-num {
margin: 0 4px;
}
.insertanychar-compact {
display: flex;
flex-wrap: wrap;
align-content: start;
align-items: center;
}
.insertanychar-compact > .oo-ui-menuOptionWidget {
padding: 0;
margin: 4px 6px;
}
.insertanychar-compact .insertanychar-name,
.insertanychar-compact .insertanychar-hex {
display: none;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-loader {
padding-inline: 8px;
}
.insertanychar .oo-ui-toggleButtonWidget:not(.oo-ui-toggleWidget-on) + .oo-ui-textInputWidget-type-search {
visibility: hidden;
}
.insertanychar-notice {
padding: 2px 6px;
display: flex;
align-items: center;
position: sticky;
top: 0;
z-index: 1;
background-color: var(--background-color-base, #fff);
border-bottom: 1px solid var(--border-color-subtle, #c8ccd1);
}
.insertanychar-notice a {
font-weight: bold;
}`;
await mw.loader.using([
'oojs-ui-core', 'jquery.textSelection', 'ext.wikiEditor.realtimepreview',
'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-editing-styling', 'oojs-ui.styles.icons-layout'
]);
iac = new Iac(context).toggle(true);
window.iac = iac;
let { source } = await $.get({
url: '//en.wiktionary.org/w/rest.php/v1/revision/92711675',
timeout: 30000
});
let blocks = [...source.matchAll(/^([\dA-F]+)\.\.([\dA-F]+); (.+)$/gm)]
.map(([, start, end, name]) => ({
start: parseInt(start, 16),
end: parseInt(end, 16),
name,
entries: []
}));
let blockIdx = 0;
for (let rev of ['92711695', '92711699']) {
// eslint-disable-next-line no-shadow
let { source } = await $.get({
url: `//en.wiktionary.org/w/rest.php/v1/revision/${rev}`,
timeout: 30000
});
for (let [, hex, name, cat, combClass, oldName] of source.matchAll(
/^([^;]+);([^;]*);([^;]*);([^;]*);(?:[^;]*;){6}([^;]*)/gm
)) {
let v = parseInt(hex, 16);
while (v > blocks[blockIdx].end) {
blockIdx++;
}
let block = blocks[blockIdx];
if (name?.startsWith('<')) {
if (name === '<control>') {
name = oldName;
} else {
if (block.entries.length) {
block.entries[0].end = v;
} else {
let blockName = name.match(/[^,<>]+/)?.[0].toUpperCase();
if (!blockName || blockName.endsWith(' SURROGATE')) continue;
let entry = { start: v, name: blockName };
if (blockName === 'HANGUL SYLLABLE') {
entry.isHangul = true;
}
block.entries.push(entry);
block.sample = { v, name: Iac.getCharName(entry, v) };
}
continue;
}
}
if (!name) continue;
let entry = { v, name };
if (cat === 'Mn') {
entry.combining = 1 + ['233', '234'].includes(combClass);
}
block.entries.push(entry);
if (!block.sample && !['C', 'Z'].includes(cat[0])) {
block.sample = entry;
}
}
}
iac.ready(blocks);
};
mw.hook('wikiEditor.toolbarReady').add(async $textarea => {
$textarea.closest('.wikiEditor-ui').toggleClass(
'insertanychar-available',
mw.config.get('wgPageContentModel') !== 'wikitext' || (
await mw.loader.using('user.options'),
!Number(mw.user.options.get('wikieditor-realtimepreview'))
)
);
$textarea.wikiEditor('addToToolbar', {
section: 'secondary',
group: 'default',
tools: {
insertanychar: {
label: 'InsertAnyChar',
type: 'toggle',
oouiIcon: 'specialCharacter',
action: { type: 'callback', execute: init }
}
}
});
});
mw.hook('ext.WikiEditor.realtimepreview.disable').add(rtp => {
rtp.context.$ui.addClass('insertanychar-available');
});
mw.hook('ext.WikiEditor.realtimepreview.enable').add(rtp => {
rtp.context.$ui.removeClass('insertanychar-available');
});
}());
f50wq42uw2fvljf0jyk52rsa32i3u45
783559
783558
2026-11-08T18:39:04Z
Nardog
40946
783559
javascript
text/javascript
window.iac?.toggle(false);
$('.insertanychar, .tool[rel="insertanychar"]').remove();
$('head > style').filter(function () {
return this.textContent.includes('.insertanychar');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(function () {
let clicked, iac;
class Iac {
constructor(context) {
this.context = context;
this.history = new OO.ui.SelectWidget({
classes: ['insertanychar-history']
}).on('choose', item => {
this.history.unselectItem(item);
this.insert(item.getData());
});
this.historyToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'history',
invisibleLabel: true,
label: 'History',
value: true
}).toggle().connect(this.history, { change: 'toggle' });
this.chars = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.chars.select.unselectItem(item);
if (this.chars.loaders.includes(item)) {
this.renderChars(item);
return;
}
this.insert(item.getData());
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter characters',
value: true
}).connect(this, { change: ['onFilterToggle', 'chars'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter characters'
}).on('change', OO.ui.debounce(this.filterChars, 500)),
filterMethod: 'filterChars',
prevBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'previous',
invisibleLabel: true,
label: 'Previous block'
}).toggle().connect(this, { click: ['showAdjacentBlock', true] }),
$blockLink: $('<a>').attr({
class: 'external',
rel: 'nofollow',
title: 'Open Unicode PDF',
target: '_blank'
}),
$info: $('<span>').addClass('insertanychar-info'),
nextBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'next',
invisibleLabel: true,
label: 'Next block'
}).toggle().connect(this, { click: 'showAdjacentBlock' }),
reset: new OO.ui.ButtonWidget({
flags: ['destructive'],
framed: false,
label: 'Reset',
title: 'Show all characters'
}).toggle().connect(this, { click: 'resetBlock' })
};
this.compactToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'viewCompact',
invisibleLabel: true,
label: 'Compact view'
}).on('change', on => {
let items = this.chars.select.getItems();
let firstVisible = this.getFirstVisible('chars', items);
this.chars.select.$element.toggleClass('insertanychar-compact', on);
let lang = this.options.lang.getValue();
items.forEach(item => {
if (!this.chars.loaders.includes(item)) {
this.renderChar(item, on, lang);
}
});
this.scroll('chars', firstVisible, {
alignToTop: true,
animate: false
});
});
this.chars.$notice = $('<div>').addClass('insertanychar-notice').append(
this.chars.prevBlock.$element,
$('<span>').addClass('insertanychar-name').append(
this.chars.$blockLink,
this.chars.$info
),
this.chars.nextBlock.$element,
this.chars.reset.$element
);
this.chars.tab = new OO.ui.TabPanelLayout('chars', {
$content: $([
this.chars.$notice[0],
this.chars.select.$element[0]
]),
label: 'Characters'
}).connect(this.chars.filterToggle, { active: 'toggle' })
.connect(this.chars.filter, { active: 'toggle' })
.connect(this.compactToggle, { active: 'toggle' });
this.blocks = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.blocks.select.unselectItem(item);
this.showBlock(item.getData());
this.index.setTabPanel('chars');
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter blocks',
value: true
}).toggle().connect(this, { change: ['onFilterToggle', 'blocks'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter blocks'
}).toggle().on('change', OO.ui.debounce(this.filterBlocks, 250)),
filterMethod: 'filterBlocks',
$notice: $('<div>').addClass('insertanychar-notice insertanychar-info')
};
this.blocks.tab = new OO.ui.TabPanelLayout('blocks', {
$content: $([
this.blocks.$notice[0],
this.blocks.select.$element[0]
]),
label: 'Blocks'
}).connect(this.blocks.filterToggle, { active: 'toggle' })
.connect(this.blocks.filter, { active: 'toggle' });
this.index = new OO.ui.IndexLayout({
autoFocus: false,
framed: false
}).addTabPanels([this.chars.tab, this.blocks.tab]).on('set', tab => {
let side = tab.getName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
});
this.options = {
font: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: 0,
label: 'Serif first'
}),
new OO.ui.MenuOptionWidget({
data: 1,
label: 'Sans-serif first'
})
]
}
}),
lang: new OO.ui.ComboBoxInputWidget({
$overlay: true,
options: [
{ data: '', label: '(Default)' },
{ data: 'ja' },
{ data: 'ko' },
{ data: 'vi' },
{ data: 'zh-Hans' },
{ data: 'zh-Hant' },
{ data: 'zh-Hant-HK' },
{ data: 'zh-Hant-TW' }
],
placeholder: '(Default)'
}).on('change', on => {
this.$pane.find('.insertanychar-char').attr('lang', on || null);
}),
vs: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: '',
label: 'None'
})
]
}
})
};
this.options.font.getMenu().selectItemByData(0).on('choose', item => {
this.$pane.toggleClass('insertanychar-sans', !!item.getData());
});
this.options.vs.getMenu().selectItemByData('').on('choose', item => {
let v = item.getData();
this.$pane.css(
'--insertanychar-vs',
v ? `'\\${v.toString(16)}'` : ''
);
});
this.options.panel = new OO.ui.PanelLayout({
$content: $([
new OO.ui.FieldLayout(this.options.font, {
label: 'Font stack:'
}).$element[0],
new OO.ui.FieldLayout(this.options.lang, {
label: 'Language:'
}).$element[0],
new OO.ui.FieldLayout(this.options.vs, {
label: 'Variation selector:'
}).$element[0]
]),
expanded: false,
padded: true
}).toggle();
this.optionsToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'textStyle',
invisibleLabel: true,
label: 'Preview options'
}).connect(this.options.panel, { change: 'toggle' });
this.chars.loaders = [
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
}),
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
})
];
this.chars.observer = new IntersectionObserver(entries => {
if (!entries[0].isIntersecting) return;
this.renderChars(this.chars.loaders.find(loader => (
loader.$element.is(entries[0].target))
));
}, { threshold: 0.75 });
this.chars.loaders.forEach(loader => {
this.chars.observer.observe(loader.$element[0]);
});
this.twoPaneLayout = new mw.loader.moduleRegistry['ext.wikiEditor.realtimepreview']
.packageExports['realtimepreview/TwoPaneLayout.js']();
this.$pane = this.twoPaneLayout.getPane2()
.addClass('insertanychar insertanychar-loading')
.text('Loading...');
}
ready(blocks) {
this.chars.count = 0;
this.blocks.blocks = blocks;
this.blocks.count = blocks.length;
let vs = [];
this.blocks.items = blocks.map((block, i) => {
if (block.name.startsWith('Variation Selectors')) {
vs.push(...block.entries);
}
let { sample } = block;
delete block.sample;
let $sample = $('<span>').addClass('insertanychar-char');
if (sample) {
$sample.text(Iac.renderChar(sample.v, sample.combining));
}
block.count = Iac.isRange(block.entries[0])
? block.entries[0].end - block.entries[0].start
: block.entries.length;
this.chars.count += block.count;
return new OO.ui.MenuOptionWidget({
data: block,
disabled: !block.count,
label: $([
$sample[0],
$('<span>').addClass('insertanychar-name').text(
block.name
).append(
$('<span>').addClass('insertanychar-info').text(
`${block.count.toLocaleString('en')} characters`
)
)[0],
$('<span>').addClass('insertanychar-num').text(i + 1)[0]
])
});
});
this.blocks.select.addItems(this.blocks.items);
this.options.vs.getMenu().addItems(vs.map(entry => (
new OO.ui.MenuOptionWidget({
data: entry.v,
label: `${entry.name.slice(19)} (${Iac.getHex(entry.v)})`
})
)));
this.blocks.$notice
.text(`${this.blocks.count.toLocaleString('en')} blocks`);
this.$pane.empty().removeClass('insertanychar-loading').append(
this.history.$element,
this.index.$element,
this.options.panel.$element,
$('<div>').addClass('insertanychar-bar').append(
this.historyToggle.$element,
this.chars.filterToggle.$element,
this.chars.filter.$element,
this.blocks.filterToggle.$element,
this.blocks.filter.$element,
this.optionsToggle.$element,
this.compactToggle.$element
)
).on('keydown', e => {
if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
let isEnter = e.which === 13;
let isUp = e.which === 38;
let isDown = e.which === 40;
let isHome = e.which === 36;
let isEnd = e.which === 35;
let isPgUp = e.which === 33;
let isPgDown = e.which === 34;
if (!(isEnter || isUp || isDown || isHome || isEnd || isPgUp || isPgDown)) return;
e.preventDefault();
let side = this.index.getCurrentTabPanelName();
if (isEnter) {
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
this[side].select.chooseItem(highlighted);
}
return;
}
let item = (isUp || isDown) && this.getAdjacent(side, isUp) ||
(isPgUp || isPgDown) && this.getPageAway(side, isPgUp) ||
this.getEdge(side, isDown || isPgUp || isHome);
if (item) {
this[side].select.highlightItem(item);
this.scroll(side, item);
}
});
this.filterChars(true);
if (!OO.ui.isMobile()) {
this.chars.filter.focus();
}
}
toggle(visible = !this.visible) {
let $uiText = this.context.$ui
.toggleClass('insertanychar-enabled', visible)
.find('.wikiEditor-ui-text');
if (visible) {
$uiText.before(this.twoPaneLayout.$element);
this.twoPaneLayout.setPane1($uiText);
this.twoPaneLayout.$element.show();
this.twoPaneLayout.$element.css('height', $uiText.height() + 'px');
$uiText.css('height', '100%');
let side = this.index.getCurrentTabPanelName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
} else {
$uiText.css('height', this.twoPaneLayout.$element.height() + 'px');
this.twoPaneLayout.$element.after($uiText).hide();
}
this.visible = visible;
this.context.$ui.find('.tool[rel="insertanychar"]').data('ooui')
.setFlags({ progressive: visible });
return this;
}
insert(data) {
this.context.$textarea.textSelection('encapsulateSelection', {
peri: String.fromCodePoint(data.v),
selectPeri: false
});
let historyItem = this.history.getItems().find(item => (
item.getData().v === data.v
)) || this.renderChar(data, true);
this.history.addItems([historyItem], 0);
this.historyToggle.toggle(true);
}
filterChars = first => {
let prevSearch = this.chars.search;
this.chars.search = new Search(
this.chars.filter.getValue(),
this.chars.filterToggle.getValue()
);
if (!first && this.block === this.prevBlock &&
this.chars.search.compare(prevSearch)
) {
return;
}
this.prevBlock = this.block;
this.chars.entries = this.block
? this.block.entries
: this.blocks.blocks.flatMap(block => block.entries);
if (!this.chars.search.valid) {
this.chars.$info.text(`${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars(null, !this.block);
return;
}
let matches = [[], [], [], [], [], []];
this.chars.entries.forEach(entry => {
if (!Iac.isRange(entry)) {
let result = this.chars.search.test(entry.name);
if (result) {
matches[result - 1].push(entry);
}
return;
}
if (!entry.isHangul && !this.chars.search.testHex(entry.name)) return;
for (let v = entry.start; v <= entry.end; v++) {
let nameToTest = Iac.getCharName(entry, v, true);
let result = this.chars.search.test(nameToTest);
if (result) {
matches[result - 1].push({
v,
name: entry.isHangul ? nameToTest : entry.name
});
}
}
});
this.chars.entries = matches.flat();
this.chars.$info.text(`${
this.chars.entries.length.toLocaleString('en')
} / ${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars();
};
renderChars(loader, startMid) {
let compact = this.compactToggle.getValue();
let lang = this.options.lang.getValue();
let prevItems = this.chars.select.getItems();
let prevCharItems = prevItems
.filter(item => !this.chars.loaders.includes(item));
let i = 0, blockV;
if (startMid) {
let firstVisible = this.getFirstVisible('chars', prevCharItems);
if (firstVisible) {
let data = firstVisible.getData();
this.chars.entries.some((entry, j) => {
if (Iac.isRange(entry)) {
if (Iac.inBlock(data.v, entry)) {
i = j;
blockV = data.v;
return true;
}
} else if (entry.v === data.v) {
i = j;
return true;
}
});
}
}
let up = loader === this.chars.loaders[0];
let increment = up ? -1 : 1;
if (loader) {
this.chars.select.removeItems(this.chars.loaders);
let adjacent = prevCharItems.at(up ? 0 : -1);
if (adjacent) {
let data = adjacent.getData();
if (Iac.isRange(data.entry)) {
i = this.chars.entries.indexOf(data.entry);
blockV = data.v + increment;
} else {
i = this.chars.entries.indexOf(data.entry) + increment;
}
}
} else {
this.chars.select.removeItems(prevItems);
}
let toAdd = [], len = 0;
do {
let entry = this.chars.entries[i];
if (!entry) break;
if (!Iac.isRange(entry)) {
len = toAdd.push(this.renderChar({
v: entry.v,
entry
}, compact, lang));
i += increment;
continue;
}
if (typeof blockV === 'number') {
if (!Iac.inBlock(blockV, entry)) {
blockV = null;
i += increment;
continue;
}
} else {
blockV = entry[up ? 'end' : 'start'];
}
len = toAdd.push(this.renderChar({
v: blockV,
entry
}, compact, lang));
blockV += increment;
} while (len < 128);
if (loader) {
let toRemove = up
? prevCharItems.slice(384 - len)
: prevCharItems.slice(0, -384 + len);
this.chars.select.removeItems(toRemove);
}
if (up) {
this.chars.select.addItems(toAdd.reverse(), 0);
} else {
this.chars.select.addItems(toAdd);
}
let reached = {};
let items = this.chars.select.getItems();
if (items.length) {
[['start', 0], ['end', -1]].forEach(([pos, idx]) => {
let data = items.at(idx).getData();
reached[pos] = data.entry === this.chars.entries.at(idx) && (
!Iac.isRange(data.entry) || data.v === data.entry[pos]
);
});
} else {
reached.start = true;
reached.end = true;
}
if (!reached.start) {
this.chars.select.addItems([this.chars.loaders[0]], 0);
}
if (!reached.end) {
this.chars.select.addItems([this.chars.loaders[1]]);
}
if (loader) {
this.scroll('chars', toAdd.at(up ? -1 : 0), {
alignToTop: up,
animate: false
});
} else {
this.chars.tab.$element[0].scrollTop = 0;
}
}
renderChar(item, compact, lang = this.options.lang.getValue()) {
let data;
if ($.isPlainObject(item)) {
data = item;
item = new OO.ui.MenuOptionWidget({ data });
data.name = Iac.getCharName(data.entry, data.v).replace(
/ (?:AND|AS|AT|BY|FOR|FROM|IN|OF|ON|OR|THE|TO|WITH)(?= )/g,
s => s.toLowerCase()
).replace(
/\b(?!CJK\b)([A-Z])([A-Z]+)\b/g,
($0, $1, $2) => $1 + $2.toLowerCase()
);
} else {
data = item.getData();
}
let block = this.block ||
this.blocks.blocks.find(b => Iac.inBlock(data.v, b));
let hex = Iac.getHex(data.v);
let $label = item.getLabel() || $('<span>').attr({
class: 'insertanychar-char',
lang: lang
}).text(Iac.renderChar(data.v, data.entry.combining));
if (!compact && !$label.children().length) {
$label = $([
$label[0],
$('<span>').addClass('insertanychar-name').text(
data.name
).append(
!this.block && $('<span>').addClass('insertanychar-block').append(
$('<a>').attr({
href: '#',
title: 'Show only this block'
}).text(block.name)
.on('click', block, this.onBlockClick)
.on('mousedown', Iac.stopPropagation)
)
)[0],
$('<span>').addClass('insertanychar-hex').text(hex)[0]
]);
}
return item.setLabel($label).setTitle(
compact ? `${data.name} (${hex})\n${block.name}` : null
);
}
showBlock(block) {
this.prevBlock = this.block;
this.block = block;
this.chars.prevBlock.toggle(true);
this.chars.$blockLink.attr(
'href',
`https://www.unicode.org/charts/PDF/U${Iac.getHex(block.start)}.pdf`
).text(block.name).show();
this.chars.reset.toggle(true);
this.chars.nextBlock.toggle(true);
if (this.chars.filterToggle.getValue()) {
this.chars.filterToggle.setValue(false);
} else {
this.filterChars();
}
}
showAdjacentBlock(prev) {
let i = this.blocks.blocks.indexOf(this.block) + (prev ? -1 : 1);
let block = this.blocks.blocks.at(i) || this.blocks.blocks[0];
this.showBlock(block);
}
resetBlock() {
this.prevBlock = this.block;
this.block = null;
this.chars.prevBlock.toggle(false);
this.chars.$blockLink.hide();
this.chars.nextBlock.toggle(false);
this.chars.reset.toggle(false);
this.filterChars();
}
filterBlocks = () => {
let prevSearch = this.blocks.search;
this.blocks.search = new Search(
this.blocks.filter.getValue(),
this.blocks.filterToggle.getValue()
);
if (this.blocks.search.compare(prevSearch)) return;
let items = this.blocks.select.getItems(), firstVisible;
if (prevSearch?.valid && !this.blocks.search.valid) {
firstVisible = this.getFirstVisible('chars', items);
}
let filtered = this.blocks.search?.valid;
let toShow = this.blocks.items.filter(item => !filtered || (
this.blocks.search.test(item.getData().name.toUpperCase())
));
this.blocks.select.removeItems(items).addItems(toShow);
this.blocks.$notice.text(`${
filtered ? `${toShow.length.toLocaleString('en')} / ` : ''
}${this.blocks.count.toLocaleString('en')} blocks`);
if (toShow.includes(firstVisible)) {
this.scroll('blocks', firstVisible, {
alignToTop: true,
animate: false
});
} else {
this.blocks.tab.$element[0].scrollTop = 0;
}
};
getFirstVisible(side, items) {
let y = this[side].tab.$element[0].scrollTop;
return items.find(item => item.$element[0].offsetTop >= y);
}
getEdge(side, up) {
return this[side].select.getItems().at(up ? 0 : -1);
}
getAdjacent(side, up) {
let items = this[side].select.getItems();
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
return items[items.indexOf(highlighted) + (up ? -1 : 1)];
}
let y = this[side].tab.$element[0].scrollTop;
if (up) {
return items.findLast(item => item.$element[0].offsetTop < y);
}
y += this[side].tab.$element[0].clientHeight;
return items.find(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight > y
));
}
getPageAway(side, up) {
let items = this[side].select.getItems();
let y = this[side].tab.$element[0].scrollTop +
this[side].tab.$element[0].clientHeight * (up ? -1 : 2);
if (up) {
return items.find(item => item.$element[0].offsetTop >= y);
}
return items.findLast(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight < y
));
}
scroll(side, item, options = {}) {
if (!item) return;
options.padding = { top: this[side].$notice[0].offsetHeight };
item.scrollElementIntoView(options);
}
onFilterToggle(side, on) {
this[this[side].filterMethod]();
if (on && !OO.ui.isMobile()) {
setTimeout(() => {
this[side].filter.focus();
});
}
}
onBlockClick = e => {
e.preventDefault();
this.showBlock(e.data);
};
static stopPropagation(e) {
e.stopPropagation();
}
static getHex(v) {
return v.toString(16).toUpperCase().padStart(4, '0');
}
// https://www.unicode.org/versions/latest/core-spec/chapter-3/
static hangul = {
leads: [
'G', 'GG', 'N', 'D', 'DD', 'R', 'M', 'B', 'BB', 'S', 'SS', '',
'J', 'JJ', 'C', 'K', 'T', 'P', 'H'
],
vowels: [
'A', 'AE', 'YA', 'YAE', 'EO', 'E', 'YEO', 'YE', 'O', 'WA',
'WAE', 'OE', 'YO', 'U', 'WEO', 'WE', 'WI', 'YU', 'EU', 'YI', 'I'
],
trails: [
'', 'G', 'GG', 'GS', 'N', 'NJ', 'NH', 'D', 'L', 'LG', 'LM',
'LB', 'LS', 'LT', 'LP', 'LH', 'M', 'B', 'BS', 'S', 'SS', 'NG',
'J', 'C', 'K', 'T', 'P', 'H'
]
};
static getCharName(entry, v, appendHex) {
if (entry.isHangul) {
let i = v - entry.start;
return entry.name + ' ' + Iac.hangul.leads[Math.floor(i / 588)] +
Iac.hangul.vowels[Math.floor((i % 588) / 28)] +
Iac.hangul.trails[i % 28];
}
if (appendHex) {
return entry.name + ' ' + Iac.getHex(v);
}
return entry.name;
}
static renderChar(v, combining) {
return `${combining ? '◌' : ''}${
String.fromCodePoint(v)
}${combining === 2 ? '◌' : ''}`;
}
static inBlock(v, block) {
return v >= block.start && v <= block.end;
}
static isRange(entry) {
return !!entry && Object.hasOwn(entry, 'start');
}
}
class Search {
constructor(query, valid) {
let terms = [], negTerms = new Set();
if (valid) {
query.toUpperCase().replace(/[^-\s\dA-Z]/g, '').split(/\s+/).forEach(s => {
if (s.startsWith('-')) {
if (s !== '-') {
negTerms.add(s.slice(1));
}
} else {
terms.push(...s.split('-').filter(Boolean));
}
});
}
this.negTerms = [...negTerms].sort();
this.terms = terms.filter(t => (
!this.negTerms.some(nt => t.includes(nt))
)).sort();
this.termCount = terms.length;
this.valid = !!(this.termCount || this.negTerms.length);
}
test(s) {
if (this.negTerms.some(term => s.includes(term))) return;
let words = s.split(/[ -]/);
let exactCount = 0, initialCount = 0, partialCount = 0;
this.terms.filter(term => {
let i = words.indexOf(term);
if (i === -1) {
return true;
}
words.splice(i, 1);
exactCount++;
}).filter(term => {
let i = words.findIndex(word => word.startsWith(term));
if (i === -1) {
return true;
}
words.splice(i, 1);
initialCount++;
}).forEach(term => {
let i = words.findIndex(word => word.includes(term));
if (i === -1) return;
words.splice(i, 1);
partialCount++;
});
switch (this.termCount) {
case exactCount:
return 1;
case exactCount && initialCount && exactCount + initialCount + partialCount:
return 2;
case exactCount && exactCount + partialCount:
return 3;
case initialCount:
return 4;
case initialCount && initialCount + partialCount:
return 5;
case partialCount:
return 6;
}
}
testHex(s) {
return !this.negTerms.some(term => s.includes(term)) &&
this.terms.every(term => s.includes(term) || /^[\dA-F]{1,6}$/.test(term));
}
compare(search) {
if (!search) {
return !this.valid;
}
return search.valid === this.valid &&
String(search.terms) === String(this.terms) &&
String(search.negTerms) === String(this.negTerms);
}
}
let css = mw.loader.addStyleTag(`.wikiEditor-ui:not(.insertanychar-available) .tool[rel="insertanychar"],
.wikiEditor-ui.insertanychar-enabled .tool[rel="realtimepreview"] {
display: none;
}`);
let init = async context => {
if (clicked) {
if (iac) {
iac.context = context;
iac.toggle();
}
return;
}
clicked = true;
css.textContent += `
.insertanychar-loading {
padding: 8px !important;
}
.insertanychar-bar {
display: flex;
}
.insertanychar-bar > .oo-ui-widget {
margin: 0 !important;
}
.insertanychar-history {
display: flex;
overflow: auto;
}
.insertanychar-history > .oo-ui-menuOptionWidget {
padding: 0;
margin: 2px;
}
.insertanychar-history > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label,
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
overflow: visible;
}
.insertanychar > .oo-ui-indexLayout {
flex-grow: 1;
position: relative;
}
.insertanychar .oo-ui-tabSelectWidget {
display: flex;
justify-content: space-evenly;
}
.insertanychar .oo-ui-tabOptionWidget {
margin-inline: 0;
width: 45%;
text-align: center;
}
.insertanychar .oo-ui-tabPanelLayout {
padding: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget {
padding-inline: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
display: flex;
align-items: center;
}
.insertanychar-char {
display: inline-block;
font-size: 200%;
min-width: 1.5em;
min-height: 1.5em;
line-height: 1.5;
text-align: center;
white-space: pre;
font-family: 'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif,'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif;
}
.insertanychar-sans .insertanychar-char {
font-family: 'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif,'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif;
}
.insertanychar-char::after {
content: var(--insertanychar-vs);
}
.insertanychar-history .insertanychar-char {
font-size: 150%;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-char {
margin: 0 2px;
}
.insertanychar-name {
flex-grow: 1;
white-space: normal !important;
word-break: break-word;
}
.insertanychar-block,
.insertanychar-info {
display: block;
}
.insertanychar-block > a,
.insertanychar-block > a:visited,
.insertanychar-info,
.insertanychar-hex,
.insertanychar-num {
font-size: 85%;
color: var(--color-subtle, #54595d);
}
.insertanychar-hex,
.insertanychar-num {
margin: 0 4px;
}
.insertanychar-compact {
display: flex;
flex-wrap: wrap;
align-content: start;
align-items: center;
}
.insertanychar-compact > .oo-ui-menuOptionWidget {
padding: 0;
margin: 4px 6px;
}
.insertanychar-compact .insertanychar-name,
.insertanychar-compact .insertanychar-hex {
display: none;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-loader {
padding-inline: 8px;
}
.insertanychar .oo-ui-toggleButtonWidget:not(.oo-ui-toggleWidget-on) + .oo-ui-textInputWidget-type-search {
visibility: hidden;
}
.insertanychar-notice {
padding: 2px 6px;
display: flex;
align-items: center;
position: sticky;
top: 0;
z-index: 1;
background-color: var(--background-color-base, #fff);
border-bottom: 1px solid var(--border-color-subtle, #c8ccd1);
}
.insertanychar-notice a {
font-weight: bold;
}`;
await mw.loader.using([
'oojs-ui-core', 'jquery.textSelection', 'ext.wikiEditor.realtimepreview',
'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-editing-styling', 'oojs-ui.styles.icons-layout'
]);
iac = new Iac(context).toggle(true);
window.iac = iac;
let { source } = await $.get({
url: '//en.wiktionary.org/w/rest.php/v1/revision/92711675',
timeout: 30000
});
let blocks = [...source.matchAll(/^([\dA-F]+)\.\.([\dA-F]+); (.+)$/gm)]
.map(([, start, end, name]) => ({
start: parseInt(start, 16),
end: parseInt(end, 16),
name,
entries: []
}));
let blockIdx = 0;
for (let rev of ['92711695', '92711699']) {
// eslint-disable-next-line no-shadow
let { source } = await $.get({
url: `//en.wiktionary.org/w/rest.php/v1/revision/${rev}`,
timeout: 30000
});
for (let [, hex, name, cat, combClass, oldName] of source.matchAll(
/^([^;]+);([^;]*);([^;]*);([^;]*);(?:[^;]*;){6}([^;]*)/gm
)) {
let v = parseInt(hex, 16);
while (v > blocks[blockIdx].end) {
blockIdx++;
}
let block = blocks[blockIdx];
if (name?.startsWith('<')) {
if (name === '<control>') {
name = oldName;
} else {
if (block.entries.length) {
block.entries[0].end = v;
} else {
let blockName = name.match(/[^,<>]+/)?.[0].toUpperCase();
if (!blockName || blockName.endsWith(' SURROGATE')) continue;
let entry = { start: v, name: blockName };
if (blockName === 'HANGUL SYLLABLE') {
entry.isHangul = true;
}
block.entries.push(entry);
block.sample = { v, name: Iac.getCharName(entry, v) };
}
continue;
}
}
if (!name) continue;
let entry = { v, name };
if (cat === 'Mn') {
entry.combining = 1 + ['233', '234'].includes(combClass);
}
block.entries.push(entry);
if (!block.sample && !['C', 'Z'].includes(cat[0])) {
block.sample = entry;
}
}
}
iac.ready(blocks);
};
mw.hook('wikiEditor.toolbarReady').add(async $textarea => {
$textarea.closest('.wikiEditor-ui').toggleClass(
'insertanychar-available',
mw.config.get('wgPageContentModel') !== 'wikitext' || (
await mw.loader.using('user.options'),
!Number(mw.user.options.get('wikieditor-realtimepreview'))
)
);
$textarea.wikiEditor('addToToolbar', {
section: 'secondary',
group: 'default',
tools: {
insertanychar: {
label: 'InsertAnyChar',
type: 'toggle',
oouiIcon: 'specialCharacter',
action: { type: 'callback', execute: init }
}
}
});
});
mw.hook('ext.WikiEditor.realtimepreview.disable').add(rtp => {
rtp.context.$ui.addClass('insertanychar-available');
});
mw.hook('ext.WikiEditor.realtimepreview.enable').add(rtp => {
rtp.context.$ui.removeClass('insertanychar-available');
});
}());
cy8fff5jov0k8q2c4kzmzfyoamg59p3
783560
783559
2026-11-08T18:51:33Z
Nardog
40946
783560
javascript
text/javascript
window.iac?.toggle(false);
$('.insertanychar, .tool[rel="insertanychar"]').remove();
$('head > style').filter(function () {
return this.textContent.includes('.insertanychar');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(function () {
let clicked, iac;
class Iac {
constructor(context) {
this.context = context;
this.history = new OO.ui.SelectWidget({
classes: ['insertanychar-history']
}).on('choose', item => {
this.history.unselectItem(item);
this.insert(item.getData());
});
this.historyToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'history',
invisibleLabel: true,
label: 'History',
value: true
}).toggle().connect(this.history, { change: 'toggle' });
this.chars = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.chars.select.unselectItem(item);
if (this.chars.loaders.includes(item)) {
this.renderChars(item);
return;
}
this.insert(item.getData());
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter characters',
value: true
}).connect(this, { change: ['onFilterToggle', 'chars'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter characters'
}).on('change', OO.ui.debounce(this.filterChars, 500)),
filterMethod: 'filterChars',
prevBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'previous',
invisibleLabel: true,
label: 'Previous block'
}).toggle().connect(this, { click: ['showAdjacentBlock', true] }),
$blockLink: $('<a>').attr({
class: 'external',
rel: 'nofollow',
title: 'Open Unicode PDF',
target: '_blank'
}),
$info: $('<span>').addClass('insertanychar-info'),
nextBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'next',
invisibleLabel: true,
label: 'Next block'
}).toggle().connect(this, { click: 'showAdjacentBlock' }),
reset: new OO.ui.ButtonWidget({
flags: ['destructive'],
framed: false,
label: 'Reset',
title: 'Show all characters'
}).toggle().connect(this, { click: 'resetBlock' })
};
this.compactToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'viewCompact',
invisibleLabel: true,
label: 'Compact view'
}).on('change', on => {
let items = this.chars.select.getItems();
let firstVisible = this.getFirstVisible('chars', items);
this.chars.select.$element.toggleClass('insertanychar-compact', on);
let lang = this.options.lang.getValue();
items.forEach(item => {
if (!this.chars.loaders.includes(item)) {
this.renderChar(item, on, lang);
}
});
this.scroll('chars', firstVisible, {
alignToTop: true,
animate: false
});
});
this.chars.$notice = $('<div>').addClass('insertanychar-notice').append(
this.chars.prevBlock.$element,
$('<span>').addClass('insertanychar-name').append(
this.chars.$blockLink,
this.chars.$info
),
this.chars.nextBlock.$element,
this.chars.reset.$element
);
this.chars.tab = new OO.ui.TabPanelLayout('chars', {
$content: $([
this.chars.$notice[0],
this.chars.select.$element[0]
]),
label: 'Characters'
}).connect(this.chars.filterToggle, { active: 'toggle' })
.connect(this.chars.filter, { active: 'toggle' })
.connect(this.compactToggle, { active: 'toggle' });
this.blocks = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.blocks.select.unselectItem(item);
this.showBlock(item.getData());
this.index.setTabPanel('chars');
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter blocks',
value: true
}).toggle().connect(this, { change: ['onFilterToggle', 'blocks'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter blocks'
}).toggle().on('change', OO.ui.debounce(this.filterBlocks, 250)),
filterMethod: 'filterBlocks',
$notice: $('<div>').addClass('insertanychar-notice insertanychar-info')
};
this.blocks.tab = new OO.ui.TabPanelLayout('blocks', {
$content: $([
this.blocks.$notice[0],
this.blocks.select.$element[0]
]),
label: 'Blocks'
}).connect(this.blocks.filterToggle, { active: 'toggle' })
.connect(this.blocks.filter, { active: 'toggle' });
this.index = new OO.ui.IndexLayout({
autoFocus: false,
framed: false
}).addTabPanels([this.chars.tab, this.blocks.tab]).on('set', tab => {
let side = tab.getName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
});
this.options = {
font: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: 0,
label: 'Serif first'
}),
new OO.ui.MenuOptionWidget({
data: 1,
label: 'Sans-serif first'
})
]
}
}),
lang: new OO.ui.ComboBoxInputWidget({
$overlay: true,
options: [
{ data: '', label: '(Default)' },
{ data: 'ja' },
{ data: 'ko' },
{ data: 'vi' },
{ data: 'zh-Hans' },
{ data: 'zh-Hant' },
{ data: 'zh-Hant-HK' },
{ data: 'zh-Hant-TW' }
],
placeholder: '(Default)'
}).on('change', on => {
this.$pane.find('.insertanychar-char').attr('lang', on || null);
}),
vs: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: '',
label: 'None'
})
]
}
})
};
this.options.font.getMenu().selectItemByData(0).on('choose', item => {
this.$pane.toggleClass('insertanychar-sans', !!item.getData());
});
this.options.vs.getMenu().selectItemByData('').on('choose', item => {
let v = item.getData();
this.$pane.css(
'--insertanychar-vs',
v ? `'\\${v.toString(16)}'` : ''
);
});
this.options.panel = new OO.ui.PanelLayout({
$content: $([
new OO.ui.FieldLayout(this.options.font, {
label: 'Font stack:'
}).$element[0],
new OO.ui.FieldLayout(this.options.lang, {
label: 'Language:'
}).$element[0],
new OO.ui.FieldLayout(this.options.vs, {
label: 'Variation selector:'
}).$element[0]
]),
expanded: false,
padded: true
}).toggle();
this.optionsToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'textStyle',
invisibleLabel: true,
label: 'Preview options'
}).connect(this.options.panel, { change: 'toggle' });
this.chars.loaders = [
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
}),
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
})
];
this.chars.observer = new IntersectionObserver(entries => {
if (!entries[0].isIntersecting) return;
this.renderChars(this.chars.loaders.find(loader => (
loader.$element.is(entries[0].target))
));
}, { threshold: 0.75 });
this.chars.loaders.forEach(loader => {
this.chars.observer.observe(loader.$element[0]);
});
this.twoPaneLayout = new mw.loader.moduleRegistry['ext.wikiEditor.realtimepreview']
.packageExports['realtimepreview/TwoPaneLayout.js']();
this.$pane = this.twoPaneLayout.getPane2()
.addClass('insertanychar insertanychar-loading')
.text('Loading...');
}
ready(blocks) {
this.chars.count = 0;
this.blocks.blocks = blocks;
this.blocks.count = blocks.length;
let vs = [];
this.blocks.items = blocks.map((block, i) => {
if (block.name.startsWith('Variation Selectors')) {
vs.push(...block.entries);
}
let { sample } = block;
delete block.sample;
let $sample = $('<span>').addClass('insertanychar-char');
if (sample) {
$sample.text(Iac.renderChar(sample.v, sample.combining));
}
block.count = Iac.isRange(block.entries[0])
? block.entries[0].end - block.entries[0].start
: block.entries.length;
this.chars.count += block.count;
return new OO.ui.MenuOptionWidget({
data: block,
disabled: !block.count,
label: $([
$sample[0],
$('<span>').addClass('insertanychar-name').text(
block.name
).append(
$('<span>').addClass('insertanychar-info').text(
`${block.count.toLocaleString('en')} characters`
)
)[0],
$('<span>').addClass('insertanychar-num').text(i + 1)[0]
])
});
});
this.blocks.select.addItems(this.blocks.items);
this.options.vs.getMenu().addItems(vs.map(entry => (
new OO.ui.MenuOptionWidget({
data: entry.v,
label: `${entry.name.slice(19)} (${Iac.getHex(entry.v)})`
})
)));
this.blocks.$notice
.text(`${this.blocks.count.toLocaleString('en')} blocks`);
this.$pane.empty().removeClass('insertanychar-loading').append(
this.history.$element,
this.index.$element,
this.options.panel.$element,
$('<div>').addClass('insertanychar-bar').append(
this.historyToggle.$element,
this.chars.filterToggle.$element,
this.chars.filter.$element,
this.blocks.filterToggle.$element,
this.blocks.filter.$element,
this.optionsToggle.$element,
this.compactToggle.$element
)
).on('keydown', e => {
if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
let isEnter = e.which === 13;
let isUp = e.which === 38;
let isDown = e.which === 40;
let isHome = e.which === 36;
let isEnd = e.which === 35;
let isPgUp = e.which === 33;
let isPgDown = e.which === 34;
if (!(isEnter || isUp || isDown || isHome || isEnd || isPgUp || isPgDown)) return;
e.preventDefault();
let side = this.index.getCurrentTabPanelName();
if (isEnter) {
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
this[side].select.chooseItem(highlighted);
}
return;
}
let item = (isUp || isDown) && this.getAdjacent(side, isUp) ||
(isPgUp || isPgDown) && this.getPageAway(side, isPgUp) ||
this.getEdge(side, isDown || isPgUp || isHome);
if (item) {
this[side].select.highlightItem(item);
this.scroll(side, item);
}
});
this.filterChars(true);
if (!OO.ui.isMobile()) {
this.chars.filter.focus();
}
}
toggle(visible = !this.visible) {
let $uiText = this.context.$ui
.toggleClass('insertanychar-enabled', visible)
.find('.wikiEditor-ui-text');
if (visible) {
$uiText.before(this.twoPaneLayout.$element);
this.twoPaneLayout.setPane1($uiText);
this.twoPaneLayout.$element.show();
this.twoPaneLayout.$element.css('height', $uiText.height() + 'px');
$uiText.css('height', '100%');
let side = this.index.getCurrentTabPanelName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
} else {
$uiText.css('height', this.twoPaneLayout.$element.height() + 'px');
this.twoPaneLayout.$element.after($uiText).hide();
}
this.visible = visible;
this.context.$ui.find('.tool[rel="insertanychar"]').data('ooui')
.setFlags({ progressive: visible });
return this;
}
insert(data) {
this.context.$textarea.textSelection('encapsulateSelection', {
peri: String.fromCodePoint(data.v),
selectPeri: false
});
let historyItem = this.history.getItems().find(item => (
item.getData().v === data.v
)) || this.renderChar(data, true);
this.history.addItems([historyItem], 0);
this.historyToggle.toggle(true);
}
filterChars = first => {
let prevSearch = this.chars.search;
this.chars.search = new Search(
this.chars.filter.getValue(),
this.chars.filterToggle.getValue()
);
if (!first && this.block === this.prevBlock &&
this.chars.search.compare(prevSearch)
) {
return;
}
this.prevBlock = this.block;
this.chars.entries = this.block
? this.block.entries
: this.blocks.blocks.flatMap(block => block.entries);
if (!this.chars.search.valid) {
this.chars.$info.text(`${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars(null, !this.block);
return;
}
let matches = [[], [], [], [], [], []];
this.chars.entries.forEach(entry => {
if (!Iac.isRange(entry)) {
let result = this.chars.search.test(entry.name);
if (result) {
matches[result - 1].push(entry);
}
return;
}
if (!entry.isHangul && !this.chars.search.testHex(entry.name)) return;
for (let v = entry.start; v <= entry.end; v++) {
let nameToTest = Iac.getCharName(entry, v, true);
let result = this.chars.search.test(nameToTest);
if (result) {
matches[result - 1].push({
v,
name: entry.isHangul ? nameToTest : entry.name
});
}
}
});
this.chars.entries = matches.flat();
this.chars.$info.text(`${
this.chars.entries.length.toLocaleString('en')
} / ${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars();
};
renderChars(loader, startMid) {
let compact = this.compactToggle.getValue();
let lang = this.options.lang.getValue();
let prevItems = this.chars.select.getItems();
let prevCharItems = prevItems
.filter(item => !this.chars.loaders.includes(item));
let i = 0, blockV;
if (startMid) {
let firstVisible = this.getFirstVisible('chars', prevCharItems);
if (firstVisible) {
let data = firstVisible.getData();
this.chars.entries.some((entry, j) => {
if (Iac.isRange(entry)) {
if (Iac.inBlock(data.v, entry)) {
i = j;
blockV = data.v;
return true;
}
} else if (entry.v === data.v) {
i = j;
return true;
}
});
}
}
let up = loader === this.chars.loaders[0];
let increment = up ? -1 : 1;
if (loader) {
this.chars.select.removeItems(this.chars.loaders);
let adjacent = prevCharItems.at(up ? 0 : -1);
if (adjacent) {
let data = adjacent.getData();
if (Iac.isRange(data.entry)) {
i = this.chars.entries.indexOf(data.entry);
blockV = data.v + increment;
} else {
i = this.chars.entries.indexOf(data.entry) + increment;
}
}
} else {
this.chars.select.removeItems(prevItems);
}
let toAdd = [], len = 0;
do {
let entry = this.chars.entries[i];
if (!entry) break;
if (!Iac.isRange(entry)) {
len = toAdd.push(this.renderChar({
v: entry.v,
entry
}, compact, lang));
i += increment;
continue;
}
if (typeof blockV === 'number') {
if (!Iac.inBlock(blockV, entry)) {
blockV = null;
i += increment;
continue;
}
} else {
blockV = entry[up ? 'end' : 'start'];
}
len = toAdd.push(this.renderChar({
v: blockV,
entry
}, compact, lang));
blockV += increment;
} while (len < 128);
if (loader) {
let toRemove = up
? prevCharItems.slice(384 - len)
: prevCharItems.slice(0, -384 + len);
this.chars.select.removeItems(toRemove);
}
if (up) {
this.chars.select.addItems(toAdd.reverse(), 0);
} else {
this.chars.select.addItems(toAdd);
}
let reached = {};
let items = this.chars.select.getItems();
if (items.length) {
[['start', 0], ['end', -1]].forEach(([pos, idx]) => {
let data = items.at(idx).getData();
reached[pos] = data.entry === this.chars.entries.at(idx) && (
!Iac.isRange(data.entry) || data.v === data.entry[pos]
);
});
} else {
reached.start = true;
reached.end = true;
}
if (!reached.start) {
this.chars.select.addItems([this.chars.loaders[0]], 0);
}
if (!reached.end) {
this.chars.select.addItems([this.chars.loaders[1]]);
}
if (loader) {
this.scroll('chars', toAdd.at(up ? -1 : 0), {
alignToTop: up,
animate: false
});
} else {
this.chars.tab.$element[0].scrollTop = 0;
}
}
renderChar(item, compact, lang = this.options.lang.getValue()) {
let data;
if ($.isPlainObject(item)) {
data = item;
item = new OO.ui.MenuOptionWidget({ data });
data.name = Iac.getCharName(data.entry, data.v).replace(
/ (?:AND|AS|AT|BY|FOR|FROM|IN|OF|ON|OR|THE|TO|WITH)(?= )/g,
s => s.toLowerCase()
).replace(
/\b(?!CJK\b)([A-Z])([A-Z]+)\b/g,
($0, $1, $2) => $1 + $2.toLowerCase()
);
} else {
data = item.getData();
}
let block = this.block ||
this.blocks.blocks.find(b => Iac.inBlock(data.v, b));
let hex = Iac.getHex(data.v);
let $label = item.getLabel() || $('<span>').attr({
class: 'insertanychar-char',
lang: lang
}).text(Iac.renderChar(data.v, data.entry.combining));
if (!compact && !$label.children().length) {
$label = $([
$label[0],
$('<span>').addClass('insertanychar-name').text(
data.name
).append(
!this.block && $('<span>').addClass('insertanychar-block').append(
$('<a>').attr({
href: '#',
title: 'Show only this block'
}).text(block.name)
.on('click', block, this.onBlockClick)
.on('mousedown', Iac.stopPropagation)
)
)[0],
$('<span>').addClass('insertanychar-hex').text(hex)[0]
]);
}
return item.setLabel($label).setTitle(
compact ? `${data.name} (${hex})\n${block.name}` : null
);
}
showBlock(block) {
this.prevBlock = this.block;
this.block = block;
this.chars.prevBlock.toggle(true);
this.chars.$blockLink.attr(
'href',
`https://www.unicode.org/charts/PDF/U${Iac.getHex(block.start)}.pdf`
).text(block.name).show();
this.chars.reset.toggle(true);
this.chars.nextBlock.toggle(true);
if (this.chars.filterToggle.getValue()) {
this.chars.filterToggle.setValue(false);
} else {
this.filterChars();
}
}
showAdjacentBlock(prev) {
let i = this.blocks.blocks.indexOf(this.block);
let block;
do {
i += prev ? -1 : 1;
block = this.blocks.blocks.at(i) || this.blocks.blocks[0];
} while (!block.entries.length);
this.showBlock(block);
}
resetBlock() {
this.prevBlock = this.block;
this.block = null;
this.chars.prevBlock.toggle(false);
this.chars.$blockLink.hide();
this.chars.nextBlock.toggle(false);
this.chars.reset.toggle(false);
this.filterChars();
}
filterBlocks = () => {
let prevSearch = this.blocks.search;
this.blocks.search = new Search(
this.blocks.filter.getValue(),
this.blocks.filterToggle.getValue()
);
if (this.blocks.search.compare(prevSearch)) return;
let items = this.blocks.select.getItems(), firstVisible;
if (prevSearch?.valid && !this.blocks.search.valid) {
firstVisible = this.getFirstVisible('chars', items);
}
let filtered = this.blocks.search?.valid;
let toShow = this.blocks.items.filter(item => !filtered || (
this.blocks.search.test(item.getData().name.toUpperCase())
));
this.blocks.select.removeItems(items).addItems(toShow);
this.blocks.$notice.text(`${
filtered ? `${toShow.length.toLocaleString('en')} / ` : ''
}${this.blocks.count.toLocaleString('en')} blocks`);
if (toShow.includes(firstVisible)) {
this.scroll('blocks', firstVisible, {
alignToTop: true,
animate: false
});
} else {
this.blocks.tab.$element[0].scrollTop = 0;
}
};
getFirstVisible(side, items) {
let y = this[side].tab.$element[0].scrollTop;
return items.find(item => item.$element[0].offsetTop >= y);
}
getEdge(side, up) {
return this[side].select.getItems().at(up ? 0 : -1);
}
getAdjacent(side, up) {
let items = this[side].select.getItems();
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
return items[items.indexOf(highlighted) + (up ? -1 : 1)];
}
let y = this[side].tab.$element[0].scrollTop;
if (up) {
return items.findLast(item => item.$element[0].offsetTop < y);
}
y += this[side].tab.$element[0].clientHeight;
return items.find(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight > y
));
}
getPageAway(side, up) {
let items = this[side].select.getItems();
let y = this[side].tab.$element[0].scrollTop +
this[side].tab.$element[0].clientHeight * (up ? -1 : 2);
if (up) {
return items.find(item => item.$element[0].offsetTop >= y);
}
return items.findLast(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight < y
));
}
scroll(side, item, options = {}) {
if (!item) return;
options.padding = { top: this[side].$notice[0].offsetHeight };
item.scrollElementIntoView(options);
}
onFilterToggle(side, on) {
this[this[side].filterMethod]();
if (on && !OO.ui.isMobile()) {
setTimeout(() => {
this[side].filter.focus();
});
}
}
onBlockClick = e => {
e.preventDefault();
this.showBlock(e.data);
};
static stopPropagation(e) {
e.stopPropagation();
}
static getHex(v) {
return v.toString(16).toUpperCase().padStart(4, '0');
}
// https://www.unicode.org/versions/latest/core-spec/chapter-3/
static hangul = {
leads: [
'G', 'GG', 'N', 'D', 'DD', 'R', 'M', 'B', 'BB', 'S', 'SS', '',
'J', 'JJ', 'C', 'K', 'T', 'P', 'H'
],
vowels: [
'A', 'AE', 'YA', 'YAE', 'EO', 'E', 'YEO', 'YE', 'O', 'WA',
'WAE', 'OE', 'YO', 'U', 'WEO', 'WE', 'WI', 'YU', 'EU', 'YI', 'I'
],
trails: [
'', 'G', 'GG', 'GS', 'N', 'NJ', 'NH', 'D', 'L', 'LG', 'LM',
'LB', 'LS', 'LT', 'LP', 'LH', 'M', 'B', 'BS', 'S', 'SS', 'NG',
'J', 'C', 'K', 'T', 'P', 'H'
]
};
static getCharName(entry, v, appendHex) {
if (entry.isHangul) {
let i = v - entry.start;
return entry.name + ' ' + Iac.hangul.leads[Math.floor(i / 588)] +
Iac.hangul.vowels[Math.floor((i % 588) / 28)] +
Iac.hangul.trails[i % 28];
}
if (appendHex) {
return entry.name + ' ' + Iac.getHex(v);
}
return entry.name;
}
static renderChar(v, combining) {
return `${combining ? '◌' : ''}${
String.fromCodePoint(v)
}${combining === 2 ? '◌' : ''}`;
}
static inBlock(v, block) {
return v >= block.start && v <= block.end;
}
static isRange(entry) {
return !!entry && Object.hasOwn(entry, 'start');
}
}
class Search {
constructor(query, valid) {
let terms = [], negTerms = new Set();
if (valid) {
query.toUpperCase().replace(/[^-\s\dA-Z]/g, '').split(/\s+/).forEach(s => {
if (s.startsWith('-')) {
if (s !== '-') {
negTerms.add(s.slice(1));
}
} else {
terms.push(...s.split('-').filter(Boolean));
}
});
}
this.negTerms = [...negTerms].sort();
this.terms = terms.filter(t => (
!this.negTerms.some(nt => t.includes(nt))
)).sort();
this.termCount = terms.length;
this.valid = !!(this.termCount || this.negTerms.length);
}
test(s) {
if (this.negTerms.some(term => s.includes(term))) return;
let words = s.split(/[ -]/);
let exactCount = 0, initialCount = 0, partialCount = 0;
this.terms.filter(term => {
let i = words.indexOf(term);
if (i === -1) {
return true;
}
words.splice(i, 1);
exactCount++;
}).filter(term => {
let i = words.findIndex(word => word.startsWith(term));
if (i === -1) {
return true;
}
words.splice(i, 1);
initialCount++;
}).forEach(term => {
let i = words.findIndex(word => word.includes(term));
if (i === -1) return;
words.splice(i, 1);
partialCount++;
});
switch (this.termCount) {
case exactCount:
return 1;
case exactCount && initialCount && exactCount + initialCount + partialCount:
return 2;
case exactCount && exactCount + partialCount:
return 3;
case initialCount:
return 4;
case initialCount && initialCount + partialCount:
return 5;
case partialCount:
return 6;
}
}
testHex(s) {
return !this.negTerms.some(term => s.includes(term)) &&
this.terms.every(term => s.includes(term) || /^[\dA-F]{1,6}$/.test(term));
}
compare(search) {
if (!search) {
return !this.valid;
}
return search.valid === this.valid &&
String(search.terms) === String(this.terms) &&
String(search.negTerms) === String(this.negTerms);
}
}
let css = mw.loader.addStyleTag(`.wikiEditor-ui:not(.insertanychar-available) .tool[rel="insertanychar"],
.wikiEditor-ui.insertanychar-enabled .tool[rel="realtimepreview"] {
display: none;
}`);
let init = async context => {
if (clicked) {
if (iac) {
iac.context = context;
iac.toggle();
}
return;
}
clicked = true;
css.textContent += `
.insertanychar-loading {
padding: 8px !important;
}
.insertanychar-bar {
display: flex;
}
.insertanychar-bar > .oo-ui-widget {
margin: 0 !important;
}
.insertanychar-history {
display: flex;
overflow: auto;
}
.insertanychar-history > .oo-ui-menuOptionWidget {
padding: 0;
margin: 2px;
}
.insertanychar-history > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label,
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
overflow: visible;
}
.insertanychar > .oo-ui-indexLayout {
flex-grow: 1;
position: relative;
}
.insertanychar .oo-ui-tabSelectWidget {
display: flex;
justify-content: space-evenly;
}
.insertanychar .oo-ui-tabOptionWidget {
margin-inline: 0;
width: 45%;
text-align: center;
}
.insertanychar .oo-ui-tabPanelLayout {
padding: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget {
padding-inline: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
display: flex;
align-items: center;
}
.insertanychar-char {
display: inline-block;
font-size: 200%;
min-width: 1.5em;
min-height: 1.5em;
line-height: 1.5;
text-align: center;
white-space: pre;
font-family: 'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif,'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif;
}
.insertanychar-sans .insertanychar-char {
font-family: 'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif,'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif;
}
.insertanychar-char::after {
content: var(--insertanychar-vs);
}
.insertanychar-history .insertanychar-char {
font-size: 150%;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-char {
margin: 0 2px;
}
.insertanychar-name {
flex-grow: 1;
white-space: normal !important;
word-break: break-word;
}
.insertanychar-block,
.insertanychar-info {
display: block;
}
.insertanychar-block > a,
.insertanychar-block > a:visited,
.insertanychar-info,
.insertanychar-hex,
.insertanychar-num {
font-size: 85%;
color: var(--color-subtle, #54595d);
}
.insertanychar-hex,
.insertanychar-num {
margin: 0 4px;
}
.insertanychar-compact {
display: flex;
flex-wrap: wrap;
align-content: start;
align-items: center;
}
.insertanychar-compact > .oo-ui-menuOptionWidget {
padding: 0;
margin: 4px 6px;
}
.insertanychar-compact .insertanychar-name,
.insertanychar-compact .insertanychar-hex {
display: none;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-loader {
padding-inline: 8px;
}
.insertanychar .oo-ui-toggleButtonWidget:not(.oo-ui-toggleWidget-on) + .oo-ui-textInputWidget-type-search {
visibility: hidden;
}
.insertanychar-notice {
padding: 2px 6px;
display: flex;
align-items: center;
position: sticky;
top: 0;
z-index: 1;
background-color: var(--background-color-base, #fff);
border-bottom: 1px solid var(--border-color-subtle, #c8ccd1);
}
.insertanychar-notice a {
font-weight: bold;
}`;
await mw.loader.using([
'oojs-ui-core', 'jquery.textSelection', 'ext.wikiEditor.realtimepreview',
'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-editing-styling', 'oojs-ui.styles.icons-layout'
]);
iac = new Iac(context).toggle(true);
window.iac = iac;
let { source } = await $.get({
url: '//en.wiktionary.org/w/rest.php/v1/revision/92711675',
timeout: 30000
});
let blocks = [...source.matchAll(/^([\dA-F]+)\.\.([\dA-F]+); (.+)$/gm)]
.map(([, start, end, name]) => ({
start: parseInt(start, 16),
end: parseInt(end, 16),
name,
entries: []
}));
let blockIdx = 0;
for (let rev of ['92711695', '92711699']) {
// eslint-disable-next-line no-shadow
let { source } = await $.get({
url: `//en.wiktionary.org/w/rest.php/v1/revision/${rev}`,
timeout: 30000
});
for (let [, hex, name, cat, combClass, oldName] of source.matchAll(
/^([^;]+);([^;]*);([^;]*);([^;]*);(?:[^;]*;){6}([^;]*)/gm
)) {
let v = parseInt(hex, 16);
while (v > blocks[blockIdx].end) {
blockIdx++;
}
let block = blocks[blockIdx];
if (name?.startsWith('<')) {
if (name === '<control>') {
name = oldName;
} else {
if (block.entries.length) {
block.entries[0].end = v;
} else {
let blockName = name.match(/[^,<>]+/)?.[0].toUpperCase();
if (!blockName || blockName.endsWith(' SURROGATE')) continue;
let entry = { start: v, name: blockName };
if (blockName === 'HANGUL SYLLABLE') {
entry.isHangul = true;
}
block.entries.push(entry);
block.sample = { v, name: Iac.getCharName(entry, v) };
}
continue;
}
}
if (!name) continue;
let entry = { v, name };
if (cat === 'Mn') {
entry.combining = 1 + ['233', '234'].includes(combClass);
}
block.entries.push(entry);
if (!block.sample && !['C', 'Z'].includes(cat[0])) {
block.sample = entry;
}
}
}
iac.ready(blocks);
};
mw.hook('wikiEditor.toolbarReady').add(async $textarea => {
$textarea.closest('.wikiEditor-ui').toggleClass(
'insertanychar-available',
mw.config.get('wgPageContentModel') !== 'wikitext' || (
await mw.loader.using('user.options'),
!Number(mw.user.options.get('wikieditor-realtimepreview'))
)
);
$textarea.wikiEditor('addToToolbar', {
section: 'secondary',
group: 'default',
tools: {
insertanychar: {
label: 'InsertAnyChar',
type: 'toggle',
oouiIcon: 'specialCharacter',
action: { type: 'callback', execute: init }
}
}
});
});
mw.hook('ext.WikiEditor.realtimepreview.disable').add(rtp => {
rtp.context.$ui.addClass('insertanychar-available');
});
mw.hook('ext.WikiEditor.realtimepreview.enable').add(rtp => {
rtp.context.$ui.removeClass('insertanychar-available');
});
}());
1cuppc96rlklty6g52a725livv5dll1
783561
783560
2026-11-08T18:52:38Z
Nardog
40946
783561
javascript
text/javascript
window.iac?.toggle(false);
$('.insertanychar, .tool[rel="insertanychar"]').remove();
$('head > style').filter(function () {
return this.textContent.includes('.insertanychar');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(function () {
let clicked, iac;
class Iac {
constructor(context) {
this.context = context;
this.history = new OO.ui.SelectWidget({
classes: ['insertanychar-history']
}).on('choose', item => {
this.history.unselectItem(item);
this.insert(item.getData());
});
this.historyToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'history',
invisibleLabel: true,
label: 'History',
value: true
}).toggle().connect(this.history, { change: 'toggle' });
this.chars = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.chars.select.unselectItem(item);
if (this.chars.loaders.includes(item)) {
this.renderChars(item);
return;
}
this.insert(item.getData());
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter characters',
value: true
}).connect(this, { change: ['onFilterToggle', 'chars'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter characters'
}).on('change', OO.ui.debounce(this.filterChars, 500)),
filterMethod: 'filterChars',
prevBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'previous',
invisibleLabel: true,
label: 'Previous block'
}).toggle().connect(this, { click: ['showAdjacentBlock', true] }),
$blockLink: $('<a>').attr({
class: 'external',
rel: 'nofollow',
title: 'Open Unicode PDF',
target: '_blank'
}),
$info: $('<span>').addClass('insertanychar-info'),
nextBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'next',
invisibleLabel: true,
label: 'Next block'
}).toggle().connect(this, { click: 'showAdjacentBlock' }),
reset: new OO.ui.ButtonWidget({
flags: ['destructive'],
framed: false,
label: 'Reset',
title: 'Show all characters'
}).toggle().connect(this, { click: 'resetBlock' })
};
this.compactToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'viewCompact',
invisibleLabel: true,
label: 'Compact view'
}).on('change', on => {
let items = this.chars.select.getItems();
let firstVisible = this.getFirstVisible('chars', items);
this.chars.select.$element.toggleClass('insertanychar-compact', on);
let lang = this.options.lang.getValue();
items.forEach(item => {
if (!this.chars.loaders.includes(item)) {
this.renderChar(item, on, lang);
}
});
this.scroll('chars', firstVisible, {
alignToTop: true,
animate: false
});
});
this.chars.$notice = $('<div>').addClass('insertanychar-notice').append(
this.chars.prevBlock.$element,
$('<span>').addClass('insertanychar-name').append(
this.chars.$blockLink,
this.chars.$info
),
this.chars.nextBlock.$element,
this.chars.reset.$element
);
this.chars.tab = new OO.ui.TabPanelLayout('chars', {
$content: $([
this.chars.$notice[0],
this.chars.select.$element[0]
]),
label: 'Characters'
}).connect(this.chars.filterToggle, { active: 'toggle' })
.connect(this.chars.filter, { active: 'toggle' })
.connect(this.compactToggle, { active: 'toggle' });
this.blocks = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.blocks.select.unselectItem(item);
this.showBlock(item.getData());
this.index.setTabPanel('chars');
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter blocks',
value: true
}).toggle().connect(this, { change: ['onFilterToggle', 'blocks'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter blocks'
}).toggle().on('change', OO.ui.debounce(this.filterBlocks, 250)),
filterMethod: 'filterBlocks',
$notice: $('<div>').addClass('insertanychar-notice insertanychar-info')
};
this.blocks.tab = new OO.ui.TabPanelLayout('blocks', {
$content: $([
this.blocks.$notice[0],
this.blocks.select.$element[0]
]),
label: 'Blocks'
}).connect(this.blocks.filterToggle, { active: 'toggle' })
.connect(this.blocks.filter, { active: 'toggle' });
this.index = new OO.ui.IndexLayout({
autoFocus: false,
framed: false
}).addTabPanels([this.chars.tab, this.blocks.tab]).on('set', tab => {
let side = tab.getName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
});
this.options = {
font: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: 0,
label: 'Serif first'
}),
new OO.ui.MenuOptionWidget({
data: 1,
label: 'Sans-serif first'
})
]
}
}),
lang: new OO.ui.ComboBoxInputWidget({
$overlay: true,
options: [
{ data: '', label: '(Default)' },
{ data: 'ja' },
{ data: 'ko' },
{ data: 'vi' },
{ data: 'zh-Hans' },
{ data: 'zh-Hant' },
{ data: 'zh-Hant-HK' },
{ data: 'zh-Hant-TW' }
],
placeholder: '(Default)'
}).on('change', on => {
this.$pane.find('.insertanychar-char').attr('lang', on || null);
}),
vs: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: '',
label: 'None'
})
]
}
})
};
this.options.font.getMenu().selectItemByData(0).on('choose', item => {
this.$pane.toggleClass('insertanychar-sans', !!item.getData());
});
this.options.vs.getMenu().selectItemByData('').on('choose', item => {
let v = item.getData();
this.$pane.css(
'--insertanychar-vs',
v ? `'\\${v.toString(16)}'` : ''
);
});
this.options.panel = new OO.ui.PanelLayout({
$content: $([
new OO.ui.FieldLayout(this.options.font, {
label: 'Font stack:'
}).$element[0],
new OO.ui.FieldLayout(this.options.lang, {
label: 'Language:'
}).$element[0],
new OO.ui.FieldLayout(this.options.vs, {
label: 'Variation selector:'
}).$element[0]
]),
expanded: false,
padded: true
}).toggle();
this.optionsToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'textStyle',
invisibleLabel: true,
label: 'Preview options'
}).connect(this.options.panel, { change: 'toggle' });
this.chars.loaders = [
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
}),
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
})
];
this.chars.observer = new IntersectionObserver(entries => {
if (!entries[0].isIntersecting) return;
this.renderChars(this.chars.loaders.find(loader => (
loader.$element.is(entries[0].target))
));
}, { threshold: 0.75 });
this.chars.loaders.forEach(loader => {
this.chars.observer.observe(loader.$element[0]);
});
this.twoPaneLayout = new mw.loader.moduleRegistry['ext.wikiEditor.realtimepreview']
.packageExports['realtimepreview/TwoPaneLayout.js']();
this.$pane = this.twoPaneLayout.getPane2()
.addClass('insertanychar insertanychar-loading')
.text('Loading...');
}
ready(blocks) {
this.chars.count = 0;
this.blocks.blocks = blocks;
this.blocks.count = blocks.length;
let vs = [];
this.blocks.items = blocks.map((block, i) => {
if (block.name.startsWith('Variation Selectors')) {
vs.push(...block.entries);
}
let { sample } = block;
delete block.sample;
let $sample = $('<span>').addClass('insertanychar-char');
if (sample) {
$sample.text(Iac.renderChar(sample.v, sample.combining));
}
block.count = Iac.isRange(block.entries[0])
? block.entries[0].end - block.entries[0].start
: block.entries.length;
this.chars.count += block.count;
return new OO.ui.MenuOptionWidget({
data: block,
disabled: !block.count,
label: $([
$sample[0],
$('<span>').addClass('insertanychar-name').text(
block.name
).append(
$('<span>').addClass('insertanychar-info').text(
`${block.count.toLocaleString('en')} characters`
)
)[0],
$('<span>').addClass('insertanychar-num').text(i + 1)[0]
])
});
});
this.blocks.select.addItems(this.blocks.items);
this.options.vs.getMenu().addItems(vs.map(entry => (
new OO.ui.MenuOptionWidget({
data: entry.v,
label: `${entry.name.slice(19)} (${Iac.getHex(entry.v)})`
})
)));
this.blocks.$notice
.text(`${this.blocks.count.toLocaleString('en')} blocks`);
this.$pane.empty().removeClass('insertanychar-loading').append(
this.history.$element,
this.index.$element,
this.options.panel.$element,
$('<div>').addClass('insertanychar-bar').append(
this.historyToggle.$element,
this.chars.filterToggle.$element,
this.chars.filter.$element,
this.blocks.filterToggle.$element,
this.blocks.filter.$element,
this.optionsToggle.$element,
this.compactToggle.$element
)
).on('keydown', e => {
if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
let isEnter = e.which === 13;
let isUp = e.which === 38;
let isDown = e.which === 40;
let isHome = e.which === 36;
let isEnd = e.which === 35;
let isPgUp = e.which === 33;
let isPgDown = e.which === 34;
if (!(isEnter || isUp || isDown || isHome || isEnd || isPgUp || isPgDown)) return;
e.preventDefault();
let side = this.index.getCurrentTabPanelName();
if (isEnter) {
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
this[side].select.chooseItem(highlighted);
}
return;
}
let item = (isUp || isDown) && this.getAdjacent(side, isUp) ||
(isPgUp || isPgDown) && this.getPageAway(side, isPgUp) ||
this.getEdge(side, isDown || isPgUp || isHome);
if (item) {
this[side].select.highlightItem(item);
this.scroll(side, item);
}
});
this.filterChars(true);
if (!OO.ui.isMobile()) {
this.chars.filter.focus();
}
}
toggle(visible = !this.visible) {
let $uiText = this.context.$ui
.toggleClass('insertanychar-enabled', visible)
.find('.wikiEditor-ui-text');
if (visible) {
$uiText.before(this.twoPaneLayout.$element);
this.twoPaneLayout.setPane1($uiText);
this.twoPaneLayout.$element.show();
this.twoPaneLayout.$element.css('height', $uiText.height() + 'px');
$uiText.css('height', '100%');
let side = this.index.getCurrentTabPanelName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
} else {
$uiText.css('height', this.twoPaneLayout.$element.height() + 'px');
this.twoPaneLayout.$element.after($uiText).hide();
}
this.visible = visible;
this.context.$ui.find('.tool[rel="insertanychar"]').data('ooui')
.setFlags({ progressive: visible });
return this;
}
insert(data) {
this.context.$textarea.textSelection('encapsulateSelection', {
peri: String.fromCodePoint(data.v),
selectPeri: false
});
let historyItem = this.history.getItems().find(item => (
item.getData().v === data.v
)) || this.renderChar(data, true);
this.history.addItems([historyItem], 0);
this.historyToggle.toggle(true);
}
filterChars = first => {
let prevSearch = this.chars.search;
this.chars.search = new Search(
this.chars.filter.getValue(),
this.chars.filterToggle.getValue()
);
if (!first && this.block === this.prevBlock &&
this.chars.search.compare(prevSearch)
) {
return;
}
this.prevBlock = this.block;
this.chars.entries = this.block
? this.block.entries
: this.blocks.blocks.flatMap(block => block.entries);
if (!this.chars.search.valid) {
this.chars.$info.text(`${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars(null, !this.block);
return;
}
let matches = [[], [], [], [], [], []];
this.chars.entries.forEach(entry => {
if (!Iac.isRange(entry)) {
let result = this.chars.search.test(entry.name);
if (result) {
matches[result - 1].push(entry);
}
return;
}
if (!entry.isHangul && !this.chars.search.testHex(entry.name)) return;
for (let v = entry.start; v <= entry.end; v++) {
let nameToTest = Iac.getCharName(entry, v, true);
let result = this.chars.search.test(nameToTest);
if (result) {
matches[result - 1].push({
v,
name: entry.isHangul ? nameToTest : entry.name
});
}
}
});
this.chars.entries = matches.flat();
this.chars.$info.text(`${
this.chars.entries.length.toLocaleString('en')
} / ${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars();
};
renderChars(loader, startMid) {
let compact = this.compactToggle.getValue();
let lang = this.options.lang.getValue();
let prevItems = this.chars.select.getItems();
let prevCharItems = prevItems
.filter(item => !this.chars.loaders.includes(item));
let i = 0, blockV;
if (startMid) {
let firstVisible = this.getFirstVisible('chars', prevCharItems);
if (firstVisible) {
let data = firstVisible.getData();
this.chars.entries.some((entry, j) => {
if (Iac.isRange(entry)) {
if (Iac.inBlock(data.v, entry)) {
i = j;
blockV = data.v;
return true;
}
} else if (entry.v === data.v) {
i = j;
return true;
}
});
}
}
let up = loader === this.chars.loaders[0];
let increment = up ? -1 : 1;
if (loader) {
this.chars.select.removeItems(this.chars.loaders);
let adjacent = prevCharItems.at(up ? 0 : -1);
if (adjacent) {
let data = adjacent.getData();
if (Iac.isRange(data.entry)) {
i = this.chars.entries.indexOf(data.entry);
blockV = data.v + increment;
} else {
i = this.chars.entries.indexOf(data.entry) + increment;
}
}
} else {
this.chars.select.removeItems(prevItems);
}
let toAdd = [], len = 0;
do {
let entry = this.chars.entries[i];
if (!entry) break;
if (!Iac.isRange(entry)) {
len = toAdd.push(this.renderChar({
v: entry.v,
entry
}, compact, lang));
i += increment;
continue;
}
if (typeof blockV === 'number') {
if (!Iac.inBlock(blockV, entry)) {
blockV = null;
i += increment;
continue;
}
} else {
blockV = entry[up ? 'end' : 'start'];
}
len = toAdd.push(this.renderChar({
v: blockV,
entry
}, compact, lang));
blockV += increment;
} while (len < 128);
if (loader) {
let toRemove = up
? prevCharItems.slice(384 - len)
: prevCharItems.slice(0, -384 + len);
this.chars.select.removeItems(toRemove);
}
if (up) {
this.chars.select.addItems(toAdd.reverse(), 0);
} else {
this.chars.select.addItems(toAdd);
}
let reached = {};
let items = this.chars.select.getItems();
if (items.length) {
[['start', 0], ['end', -1]].forEach(([pos, idx]) => {
let data = items.at(idx).getData();
reached[pos] = data.entry === this.chars.entries.at(idx) && (
!Iac.isRange(data.entry) || data.v === data.entry[pos]
);
});
} else {
reached.start = true;
reached.end = true;
}
if (!reached.start) {
this.chars.select.addItems([this.chars.loaders[0]], 0);
}
if (!reached.end) {
this.chars.select.addItems([this.chars.loaders[1]]);
}
if (loader) {
this.scroll('chars', toAdd.at(up ? -1 : 0), {
alignToTop: up,
animate: false
});
} else {
this.chars.tab.$element[0].scrollTop = 0;
}
}
renderChar(item, compact, lang = this.options.lang.getValue()) {
let data;
if ($.isPlainObject(item)) {
data = item;
item = new OO.ui.MenuOptionWidget({ data });
data.name = Iac.getCharName(data.entry, data.v).replace(
/ (?:AND|AS|AT|BY|FOR|FROM|IN|OF|ON|OR|THE|TO|WITH)(?= )/g,
s => s.toLowerCase()
).replace(
/\b(?!CJK\b)([A-Z])([A-Z]+)\b/g,
($0, $1, $2) => $1 + $2.toLowerCase()
);
} else {
data = item.getData();
}
let block = this.block ||
this.blocks.blocks.find(b => Iac.inBlock(data.v, b));
let hex = Iac.getHex(data.v);
let $label = item.getLabel() || $('<span>').attr({
class: 'insertanychar-char',
lang: lang
}).text(Iac.renderChar(data.v, data.entry.combining));
if (!compact && !$label.children().length) {
$label = $([
$label[0],
$('<span>').addClass('insertanychar-name').text(
data.name
).append(
!this.block && $('<span>').addClass('insertanychar-block').append(
$('<a>').attr({
href: '#',
title: 'Show only this block'
}).text(block.name)
.on('click', block, this.onBlockClick)
.on('mousedown', Iac.stopPropagation)
)
)[0],
$('<span>').addClass('insertanychar-hex').text(hex)[0]
]);
}
return item.setLabel($label).setTitle(
compact ? `${data.name} (${hex})\n${block.name}` : null
);
}
showBlock(block) {
this.prevBlock = this.block;
this.block = block;
this.chars.prevBlock.toggle(true);
this.chars.$blockLink.attr(
'href',
`https://www.unicode.org/charts/PDF/U${Iac.getHex(block.start)}.pdf`
).text(block.name).show();
this.chars.reset.toggle(true);
this.chars.nextBlock.toggle(true);
if (this.chars.filterToggle.getValue()) {
this.chars.filterToggle.setValue(false);
} else {
this.filterChars();
}
}
showAdjacentBlock(prev) {
let i = this.blocks.blocks.indexOf(this.block), block;
do {
i += prev ? -1 : 1;
block = this.blocks.blocks.at(i) || this.blocks.blocks[0];
} while (!block.entries.length);
this.showBlock(block);
}
resetBlock() {
this.prevBlock = this.block;
this.block = null;
this.chars.prevBlock.toggle(false);
this.chars.$blockLink.hide();
this.chars.nextBlock.toggle(false);
this.chars.reset.toggle(false);
this.filterChars();
}
filterBlocks = () => {
let prevSearch = this.blocks.search;
this.blocks.search = new Search(
this.blocks.filter.getValue(),
this.blocks.filterToggle.getValue()
);
if (this.blocks.search.compare(prevSearch)) return;
let items = this.blocks.select.getItems(), firstVisible;
if (prevSearch?.valid && !this.blocks.search.valid) {
firstVisible = this.getFirstVisible('chars', items);
}
let filtered = this.blocks.search?.valid;
let toShow = this.blocks.items.filter(item => !filtered || (
this.blocks.search.test(item.getData().name.toUpperCase())
));
this.blocks.select.removeItems(items).addItems(toShow);
this.blocks.$notice.text(`${
filtered ? `${toShow.length.toLocaleString('en')} / ` : ''
}${this.blocks.count.toLocaleString('en')} blocks`);
if (toShow.includes(firstVisible)) {
this.scroll('blocks', firstVisible, {
alignToTop: true,
animate: false
});
} else {
this.blocks.tab.$element[0].scrollTop = 0;
}
};
getFirstVisible(side, items) {
let y = this[side].tab.$element[0].scrollTop;
return items.find(item => item.$element[0].offsetTop >= y);
}
getEdge(side, up) {
return this[side].select.getItems().at(up ? 0 : -1);
}
getAdjacent(side, up) {
let items = this[side].select.getItems();
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
return items[items.indexOf(highlighted) + (up ? -1 : 1)];
}
let y = this[side].tab.$element[0].scrollTop;
if (up) {
return items.findLast(item => item.$element[0].offsetTop < y);
}
y += this[side].tab.$element[0].clientHeight;
return items.find(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight > y
));
}
getPageAway(side, up) {
let items = this[side].select.getItems();
let y = this[side].tab.$element[0].scrollTop +
this[side].tab.$element[0].clientHeight * (up ? -1 : 2);
if (up) {
return items.find(item => item.$element[0].offsetTop >= y);
}
return items.findLast(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight < y
));
}
scroll(side, item, options = {}) {
if (!item) return;
options.padding = { top: this[side].$notice[0].offsetHeight };
item.scrollElementIntoView(options);
}
onFilterToggle(side, on) {
this[this[side].filterMethod]();
if (on && !OO.ui.isMobile()) {
setTimeout(() => {
this[side].filter.focus();
});
}
}
onBlockClick = e => {
e.preventDefault();
this.showBlock(e.data);
};
static stopPropagation(e) {
e.stopPropagation();
}
static getHex(v) {
return v.toString(16).toUpperCase().padStart(4, '0');
}
// https://www.unicode.org/versions/latest/core-spec/chapter-3/
static hangul = {
leads: [
'G', 'GG', 'N', 'D', 'DD', 'R', 'M', 'B', 'BB', 'S', 'SS', '',
'J', 'JJ', 'C', 'K', 'T', 'P', 'H'
],
vowels: [
'A', 'AE', 'YA', 'YAE', 'EO', 'E', 'YEO', 'YE', 'O', 'WA',
'WAE', 'OE', 'YO', 'U', 'WEO', 'WE', 'WI', 'YU', 'EU', 'YI', 'I'
],
trails: [
'', 'G', 'GG', 'GS', 'N', 'NJ', 'NH', 'D', 'L', 'LG', 'LM',
'LB', 'LS', 'LT', 'LP', 'LH', 'M', 'B', 'BS', 'S', 'SS', 'NG',
'J', 'C', 'K', 'T', 'P', 'H'
]
};
static getCharName(entry, v, appendHex) {
if (entry.isHangul) {
let i = v - entry.start;
return entry.name + ' ' + Iac.hangul.leads[Math.floor(i / 588)] +
Iac.hangul.vowels[Math.floor((i % 588) / 28)] +
Iac.hangul.trails[i % 28];
}
if (appendHex) {
return entry.name + ' ' + Iac.getHex(v);
}
return entry.name;
}
static renderChar(v, combining) {
return `${combining ? '◌' : ''}${
String.fromCodePoint(v)
}${combining === 2 ? '◌' : ''}`;
}
static inBlock(v, block) {
return v >= block.start && v <= block.end;
}
static isRange(entry) {
return !!entry && Object.hasOwn(entry, 'start');
}
}
class Search {
constructor(query, valid) {
let terms = [], negTerms = new Set();
if (valid) {
query.toUpperCase().replace(/[^-\s\dA-Z]/g, '').split(/\s+/).forEach(s => {
if (s.startsWith('-')) {
if (s !== '-') {
negTerms.add(s.slice(1));
}
} else {
terms.push(...s.split('-').filter(Boolean));
}
});
}
this.negTerms = [...negTerms].sort();
this.terms = terms.filter(t => (
!this.negTerms.some(nt => t.includes(nt))
)).sort();
this.termCount = terms.length;
this.valid = !!(this.termCount || this.negTerms.length);
}
test(s) {
if (this.negTerms.some(term => s.includes(term))) return;
let words = s.split(/[ -]/);
let exactCount = 0, initialCount = 0, partialCount = 0;
this.terms.filter(term => {
let i = words.indexOf(term);
if (i === -1) {
return true;
}
words.splice(i, 1);
exactCount++;
}).filter(term => {
let i = words.findIndex(word => word.startsWith(term));
if (i === -1) {
return true;
}
words.splice(i, 1);
initialCount++;
}).forEach(term => {
let i = words.findIndex(word => word.includes(term));
if (i === -1) return;
words.splice(i, 1);
partialCount++;
});
switch (this.termCount) {
case exactCount:
return 1;
case exactCount && initialCount && exactCount + initialCount + partialCount:
return 2;
case exactCount && exactCount + partialCount:
return 3;
case initialCount:
return 4;
case initialCount && initialCount + partialCount:
return 5;
case partialCount:
return 6;
}
}
testHex(s) {
return !this.negTerms.some(term => s.includes(term)) &&
this.terms.every(term => s.includes(term) || /^[\dA-F]{1,6}$/.test(term));
}
compare(search) {
if (!search) {
return !this.valid;
}
return search.valid === this.valid &&
String(search.terms) === String(this.terms) &&
String(search.negTerms) === String(this.negTerms);
}
}
let css = mw.loader.addStyleTag(`.wikiEditor-ui:not(.insertanychar-available) .tool[rel="insertanychar"],
.wikiEditor-ui.insertanychar-enabled .tool[rel="realtimepreview"] {
display: none;
}`);
let init = async context => {
if (clicked) {
if (iac) {
iac.context = context;
iac.toggle();
}
return;
}
clicked = true;
css.textContent += `
.insertanychar-loading {
padding: 8px !important;
}
.insertanychar-bar {
display: flex;
}
.insertanychar-bar > .oo-ui-widget {
margin: 0 !important;
}
.insertanychar-history {
display: flex;
overflow: auto;
}
.insertanychar-history > .oo-ui-menuOptionWidget {
padding: 0;
margin: 2px;
}
.insertanychar-history > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label,
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
overflow: visible;
}
.insertanychar > .oo-ui-indexLayout {
flex-grow: 1;
position: relative;
}
.insertanychar .oo-ui-tabSelectWidget {
display: flex;
justify-content: space-evenly;
}
.insertanychar .oo-ui-tabOptionWidget {
margin-inline: 0;
width: 45%;
text-align: center;
}
.insertanychar .oo-ui-tabPanelLayout {
padding: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget {
padding-inline: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
display: flex;
align-items: center;
}
.insertanychar-char {
display: inline-block;
font-size: 200%;
min-width: 1.5em;
min-height: 1.5em;
line-height: 1.5;
text-align: center;
white-space: pre;
font-family: 'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif,'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif;
}
.insertanychar-sans .insertanychar-char {
font-family: 'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif,'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif;
}
.insertanychar-char::after {
content: var(--insertanychar-vs);
}
.insertanychar-history .insertanychar-char {
font-size: 150%;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-char {
margin: 0 2px;
}
.insertanychar-name {
flex-grow: 1;
white-space: normal !important;
word-break: break-word;
}
.insertanychar-block,
.insertanychar-info {
display: block;
}
.insertanychar-block > a,
.insertanychar-block > a:visited,
.insertanychar-info,
.insertanychar-hex,
.insertanychar-num {
font-size: 85%;
color: var(--color-subtle, #54595d);
}
.insertanychar-hex,
.insertanychar-num {
margin: 0 4px;
}
.insertanychar-compact {
display: flex;
flex-wrap: wrap;
align-content: start;
align-items: center;
}
.insertanychar-compact > .oo-ui-menuOptionWidget {
padding: 0;
margin: 4px 6px;
}
.insertanychar-compact .insertanychar-name,
.insertanychar-compact .insertanychar-hex {
display: none;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-loader {
padding-inline: 8px;
}
.insertanychar .oo-ui-toggleButtonWidget:not(.oo-ui-toggleWidget-on) + .oo-ui-textInputWidget-type-search {
visibility: hidden;
}
.insertanychar-notice {
padding: 2px 6px;
display: flex;
align-items: center;
position: sticky;
top: 0;
z-index: 1;
background-color: var(--background-color-base, #fff);
border-bottom: 1px solid var(--border-color-subtle, #c8ccd1);
}
.insertanychar-notice a {
font-weight: bold;
}`;
await mw.loader.using([
'oojs-ui-core', 'jquery.textSelection', 'ext.wikiEditor.realtimepreview',
'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-editing-styling', 'oojs-ui.styles.icons-layout'
]);
iac = new Iac(context).toggle(true);
window.iac = iac;
let { source } = await $.get({
url: '//en.wiktionary.org/w/rest.php/v1/revision/92711675',
timeout: 30000
});
let blocks = [...source.matchAll(/^([\dA-F]+)\.\.([\dA-F]+); (.+)$/gm)]
.map(([, start, end, name]) => ({
start: parseInt(start, 16),
end: parseInt(end, 16),
name,
entries: []
}));
let blockIdx = 0;
for (let rev of ['92711695', '92711699']) {
// eslint-disable-next-line no-shadow
let { source } = await $.get({
url: `//en.wiktionary.org/w/rest.php/v1/revision/${rev}`,
timeout: 30000
});
for (let [, hex, name, cat, combClass, oldName] of source.matchAll(
/^([^;]+);([^;]*);([^;]*);([^;]*);(?:[^;]*;){6}([^;]*)/gm
)) {
let v = parseInt(hex, 16);
while (v > blocks[blockIdx].end) {
blockIdx++;
}
let block = blocks[blockIdx];
if (name?.startsWith('<')) {
if (name === '<control>') {
name = oldName;
} else {
if (block.entries.length) {
block.entries[0].end = v;
} else {
let blockName = name.match(/[^,<>]+/)?.[0].toUpperCase();
if (!blockName || blockName.endsWith(' SURROGATE')) continue;
let entry = { start: v, name: blockName };
if (blockName === 'HANGUL SYLLABLE') {
entry.isHangul = true;
}
block.entries.push(entry);
block.sample = { v, name: Iac.getCharName(entry, v) };
}
continue;
}
}
if (!name) continue;
let entry = { v, name };
if (cat === 'Mn') {
entry.combining = 1 + ['233', '234'].includes(combClass);
}
block.entries.push(entry);
if (!block.sample && !['C', 'Z'].includes(cat[0])) {
block.sample = entry;
}
}
}
iac.ready(blocks);
};
mw.hook('wikiEditor.toolbarReady').add(async $textarea => {
$textarea.closest('.wikiEditor-ui').toggleClass(
'insertanychar-available',
mw.config.get('wgPageContentModel') !== 'wikitext' || (
await mw.loader.using('user.options'),
!Number(mw.user.options.get('wikieditor-realtimepreview'))
)
);
$textarea.wikiEditor('addToToolbar', {
section: 'secondary',
group: 'default',
tools: {
insertanychar: {
label: 'InsertAnyChar',
type: 'toggle',
oouiIcon: 'specialCharacter',
action: { type: 'callback', execute: init }
}
}
});
});
mw.hook('ext.WikiEditor.realtimepreview.disable').add(rtp => {
rtp.context.$ui.addClass('insertanychar-available');
});
mw.hook('ext.WikiEditor.realtimepreview.enable').add(rtp => {
rtp.context.$ui.removeClass('insertanychar-available');
});
}());
k7tnumc78ux42dex1acycdf7bh5yxig
783646
783561
2026-11-09T03:37:38Z
Nardog
40946
783646
javascript
text/javascript
window.iac?.toggle(false);
$('.insertanychar, .tool[rel="insertanychar"]').remove();
$('head > style').filter(function () {
return this.textContent.includes('.insertanychar');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(function () {
let clicked, iac;
class Iac {
constructor(context) {
this.context = context;
this.history = new OO.ui.SelectWidget({
classes: ['insertanychar-history']
}).on('choose', item => {
this.history.unselectItem(item);
this.insert(item.getData());
});
this.historyToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'history',
invisibleLabel: true,
label: 'History',
value: true
}).toggle().connect(this.history, { change: 'toggle' });
this.chars = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.chars.select.unselectItem(item);
if (this.chars.loaders.includes(item)) {
this.renderChars(item);
return;
}
this.insert(item.getData());
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter characters',
value: true
}).connect(this, { change: ['onFilterToggle', 'chars'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter characters'
}).on('change', OO.ui.debounce(this.filterChars, 500)),
filterMethod: 'filterChars',
prevBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'previous',
invisibleLabel: true,
label: 'Previous block'
}).toggle().connect(this, { click: ['showAdjacentBlock', true] }),
$blockLink: $('<a>').attr({
class: 'external',
rel: 'nofollow',
title: 'Open Unicode PDF',
target: '_blank'
}),
$info: $('<span>').addClass('insertanychar-info'),
nextBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'next',
invisibleLabel: true,
label: 'Next block'
}).toggle().connect(this, { click: 'showAdjacentBlock' }),
reset: new OO.ui.ButtonWidget({
flags: ['destructive'],
framed: false,
label: 'Reset',
title: 'Show all characters'
}).toggle().connect(this, { click: 'resetBlock' })
};
this.compactToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'viewCompact',
invisibleLabel: true,
label: 'Compact view'
}).on('change', on => {
let items = this.chars.select.getItems();
let firstVisible = this.getFirstVisible('chars', items);
this.chars.select.$element.toggleClass('insertanychar-compact', on);
let lang = this.options.lang.getValue();
items.forEach(item => {
if (!this.chars.loaders.includes(item)) {
this.renderChar(item, on, lang);
}
});
this.scroll('chars', firstVisible, {
alignToTop: true,
animate: false
});
});
this.chars.$notice = $('<div>').addClass('insertanychar-notice').append(
this.chars.prevBlock.$element,
$('<span>').addClass('insertanychar-name').append(
this.chars.$blockLink,
this.chars.$info
),
this.chars.nextBlock.$element,
this.chars.reset.$element
);
this.chars.tab = new OO.ui.TabPanelLayout('chars', {
$content: $([
this.chars.$notice[0],
this.chars.select.$element[0]
]),
label: 'Characters'
}).connect(this.chars.filterToggle, { active: 'toggle' })
.connect(this.chars.filter, { active: 'toggle' })
.connect(this.compactToggle, { active: 'toggle' });
this.blocks = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.blocks.select.unselectItem(item);
this.showBlock(item.getData());
this.index.setTabPanel('chars');
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter blocks',
value: true
}).toggle().connect(this, { change: ['onFilterToggle', 'blocks'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter blocks'
}).toggle().on('change', OO.ui.debounce(this.filterBlocks, 250)),
filterMethod: 'filterBlocks',
$notice: $('<div>').addClass('insertanychar-notice insertanychar-info')
};
this.blocks.tab = new OO.ui.TabPanelLayout('blocks', {
$content: $([
this.blocks.$notice[0],
this.blocks.select.$element[0]
]),
label: 'Blocks'
}).connect(this.blocks.filterToggle, { active: 'toggle' })
.connect(this.blocks.filter, { active: 'toggle' });
this.index = new OO.ui.IndexLayout({
autoFocus: false,
framed: false
}).addTabPanels([this.chars.tab, this.blocks.tab]).on('set', tab => {
let side = tab.getName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
});
this.options = {
font: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: 0,
label: 'Serif first'
}),
new OO.ui.MenuOptionWidget({
data: 1,
label: 'Sans-serif first'
})
]
}
}),
lang: new OO.ui.ComboBoxInputWidget({
$overlay: true,
options: [
{ data: '', label: '(Default)' },
{ data: 'ja' },
{ data: 'ko' },
{ data: 'vi' },
{ data: 'zh-Hans' },
{ data: 'zh-Hant' },
{ data: 'zh-Hant-HK' },
{ data: 'zh-Hant-TW' }
],
placeholder: '(Default)'
}).on('change', on => {
this.$pane.find('.insertanychar-char').attr('lang', on || null);
}),
vs: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: '',
label: 'None'
})
]
}
})
};
this.options.font.getMenu().selectItemByData(0).on('choose', item => {
this.$pane.toggleClass('insertanychar-sans', !!item.getData());
});
this.options.vs.getMenu().selectItemByData('').on('choose', item => {
let v = item.getData();
this.$pane.css(
'--insertanychar-vs',
v ? `'\\${v.toString(16)}'` : ''
);
});
this.options.panel = new OO.ui.PanelLayout({
$content: $([
new OO.ui.FieldLayout(this.options.font, {
label: 'Font stack:'
}).$element[0],
new OO.ui.FieldLayout(this.options.lang, {
label: 'Language:'
}).$element[0],
new OO.ui.FieldLayout(this.options.vs, {
label: 'Variation selector:'
}).$element[0]
]),
expanded: false,
padded: true
}).toggle();
this.optionsToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'textStyle',
invisibleLabel: true,
label: 'Preview options'
}).connect(this.options.panel, { change: 'toggle' });
this.chars.loaders = [
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
}),
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
})
];
this.chars.observer = new IntersectionObserver(entries => {
if (!entries[0].isIntersecting) return;
this.renderChars(this.chars.loaders.find(loader => (
loader.$element.is(entries[0].target))
));
}, { threshold: 0.75 });
this.chars.loaders.forEach(loader => {
this.chars.observer.observe(loader.$element[0]);
});
this.twoPaneLayout = new mw.loader.moduleRegistry['ext.wikiEditor.realtimepreview']
.packageExports['realtimepreview/TwoPaneLayout.js']();
this.$pane = this.twoPaneLayout.getPane2()
.addClass('insertanychar insertanychar-loading')
.text('Loading...');
}
ready(blocks) {
this.chars.count = 0;
this.blocks.blocks = blocks;
this.blocks.count = blocks.length;
let vs = [];
this.blocks.items = blocks.map((block, i) => {
if (block.name.startsWith('Variation Selectors')) {
vs.push(...block.entries);
}
let { sample } = block;
delete block.sample;
let $sample = $('<span>').addClass('insertanychar-char');
if (sample) {
$sample.text(Iac.renderChar(sample.v, sample.combining));
}
block.count = Iac.isRange(block.entries[0])
? block.entries[0].end - block.entries[0].start
: block.entries.length;
this.chars.count += block.count;
return new OO.ui.MenuOptionWidget({
data: block,
disabled: !block.count,
label: $([
$sample[0],
$('<span>').addClass('insertanychar-name').text(
block.name
).append(
$('<span>').addClass('insertanychar-info').text(
`${block.count.toLocaleString('en')} characters`
)
)[0],
$('<span>').addClass('insertanychar-num').text(i + 1)[0]
])
});
});
this.blocks.select.addItems(this.blocks.items);
this.options.vs.getMenu().addItems(vs.map(entry => (
new OO.ui.MenuOptionWidget({
data: entry.v,
label: `${entry.name.slice(19)} (${Iac.getHex(entry.v)})`
})
)));
this.blocks.$notice
.text(`${this.blocks.count.toLocaleString('en')} blocks`);
this.$pane.empty().removeClass('insertanychar-loading').append(
this.history.$element,
this.index.$element,
this.options.panel.$element,
$('<div>').addClass('insertanychar-bar').append(
this.historyToggle.$element,
this.chars.filterToggle.$element,
this.chars.filter.$element,
this.blocks.filterToggle.$element,
this.blocks.filter.$element,
this.optionsToggle.$element,
this.compactToggle.$element
)
).on('keydown', e => {
if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
let isEnter = e.which === 13;
let isUp = e.which === 38;
let isDown = e.which === 40;
let isHome = e.which === 36;
let isEnd = e.which === 35;
let isPgUp = e.which === 33;
let isPgDown = e.which === 34;
if (!(isEnter || isUp || isDown || isHome || isEnd || isPgUp || isPgDown)) return;
e.preventDefault();
let side = this.index.getCurrentTabPanelName();
if (isEnter) {
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
this[side].select.chooseItem(highlighted);
}
return;
}
let item = (isUp || isDown) && this.getAdjacent(side, isUp) ||
(isPgUp || isPgDown) && this.getPageAway(side, isPgUp) ||
this.getEdge(side, isDown || isPgUp || isHome);
if (item) {
this[side].select.highlightItem(item);
this.scroll(side, item);
}
});
this.filterChars(true);
if (!OO.ui.isMobile()) {
this.chars.filter.focus();
}
}
toggle(visible = !this.visible) {
let $uiText = this.context.$ui
.toggleClass('insertanychar-enabled', visible)
.find('.wikiEditor-ui-text');
if (visible) {
$uiText.before(this.twoPaneLayout.$element);
this.twoPaneLayout.setPane1($uiText);
this.twoPaneLayout.$element.show();
this.twoPaneLayout.$element.css('height', $uiText.height() + 'px');
$uiText.css('height', '100%');
let side = this.index.getCurrentTabPanelName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
} else {
$uiText.css('height', this.twoPaneLayout.$element.height() + 'px');
this.twoPaneLayout.$element.after($uiText).hide();
}
this.visible = visible;
this.context.$ui.find('.tool[rel="insertanychar"]').data('ooui')
.setFlags({ progressive: visible });
return this;
}
insert(data) {
this.context.$textarea.textSelection('encapsulateSelection', {
peri: String.fromCodePoint(data.v),
selectPeri: false
});
let historyItem = this.history.getItems().find(item => (
item.getData().v === data.v
)) || this.renderChar(data, true);
this.history.addItems([historyItem], 0);
this.historyToggle.toggle(true);
}
filterChars = first => {
let prevSearch = this.chars.search;
this.chars.search = new Search(
this.chars.filter.getValue(),
this.chars.filterToggle.getValue()
);
if (!first && this.block === this.prevBlock &&
this.chars.search.compare(prevSearch)
) {
return;
}
this.prevBlock = this.block;
this.chars.entries = this.block
? this.block.entries
: this.blocks.blocks.flatMap(block => block.entries);
if (!this.chars.search.valid) {
this.chars.$info.text(`${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars(null, !this.block);
return;
}
let matches = [[], [], [], [], [], []];
this.chars.entries.forEach(entry => {
if (!Iac.isRange(entry)) {
let result = this.chars.search.test(entry.name);
if (result) {
matches[result - 1].push(entry);
}
return;
}
if (!entry.isHangul && !this.chars.search.testHex(entry.name)) return;
for (let v = entry.start; v <= entry.end; v++) {
let nameToTest = Iac.getCharName(entry, v, true);
let result = this.chars.search.test(nameToTest);
if (result) {
matches[result - 1].push({
v,
name: entry.isHangul ? nameToTest : entry.name
});
}
}
});
this.chars.entries = matches.flat();
this.chars.$info.text(`${
this.chars.entries.length.toLocaleString('en')
} / ${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars();
};
renderChars(loader, startMid) {
let compact = this.compactToggle.getValue();
let lang = this.options.lang.getValue();
let prevItems = this.chars.select.getItems();
let prevCharItems = prevItems
.filter(item => !this.chars.loaders.includes(item));
let i = 0, blockV;
if (startMid) {
let firstVisible = this.getFirstVisible('chars', prevCharItems);
if (firstVisible) {
let data = firstVisible.getData();
this.chars.entries.some((entry, j) => {
if (Iac.isRange(entry)) {
if (Iac.inBlock(data.v, entry)) {
i = j;
blockV = data.v;
return true;
}
} else if (entry.v === data.v) {
i = j;
return true;
}
});
}
}
let up = loader === this.chars.loaders[0];
let increment = up ? -1 : 1;
if (loader) {
this.chars.select.removeItems(this.chars.loaders);
let adjacent = prevCharItems.at(up ? 0 : -1);
if (adjacent) {
let data = adjacent.getData();
if (Iac.isRange(data.entry)) {
i = this.chars.entries.indexOf(data.entry);
blockV = data.v + increment;
} else {
i = this.chars.entries.indexOf(data.entry) + increment;
}
}
} else {
this.chars.select.removeItems(prevItems);
}
let toAdd = [], len = 0;
do {
let entry = this.chars.entries[i];
if (!entry) break;
if (!Iac.isRange(entry)) {
len = toAdd.push(this.renderChar({
v: entry.v,
entry
}, compact, lang));
i += increment;
continue;
}
if (typeof blockV === 'number') {
if (!Iac.inBlock(blockV, entry)) {
blockV = null;
i += increment;
continue;
}
} else {
blockV = entry[up ? 'end' : 'start'];
}
len = toAdd.push(this.renderChar({
v: blockV,
entry
}, compact, lang));
blockV += increment;
} while (len < 128);
if (loader) {
let toRemove = up
? prevCharItems.slice(384 - len)
: prevCharItems.slice(0, -384 + len);
this.chars.select.removeItems(toRemove);
}
if (up) {
this.chars.select.addItems(toAdd.reverse(), 0);
} else {
this.chars.select.addItems(toAdd);
}
let reached = {};
let items = this.chars.select.getItems();
if (items.length) {
[['start', 0], ['end', -1]].forEach(([pos, idx]) => {
let data = items.at(idx).getData();
reached[pos] = data.entry === this.chars.entries.at(idx) && (
!Iac.isRange(data.entry) || data.v === data.entry[pos]
);
});
} else {
reached.start = true;
reached.end = true;
}
if (!reached.start) {
this.chars.select.addItems([this.chars.loaders[0]], 0);
}
if (!reached.end) {
this.chars.select.addItems([this.chars.loaders[1]]);
}
if (loader) {
this.scroll('chars', toAdd.at(up ? -1 : 0), {
alignToTop: up,
animate: false
});
} else {
this.chars.tab.$element[0].scrollTop = 0;
}
}
renderChar(item, compact, lang = this.options.lang.getValue()) {
let data;
if ($.isPlainObject(item)) {
data = item;
item = new OO.ui.MenuOptionWidget({ data });
data.name = Iac.getCharName(data.entry, data.v).replace(
/ (?:AND|AS|AT|BY|FOR|FROM|IN|OF|ON|OR|THE|TO|WITH)(?= )/g,
s => s.toLowerCase()
).replace(
/\b(?!CJK\b)([A-Z])([A-Z]+)\b/g,
($0, $1, $2) => $1 + $2.toLowerCase()
);
} else {
data = item.getData();
}
let block = this.block ||
this.blocks.blocks.find(b => Iac.inBlock(data.v, b));
let hex = Iac.getHex(data.v);
let $label = item.getLabel() || $('<span>').attr({
class: 'insertanychar-char',
lang: lang
}).text(Iac.renderChar(data.v, data.entry.combining));
if (!compact && !$label.children().length) {
$label = $([
$label[0],
$('<span>').addClass('insertanychar-name').text(
data.name
).append(
!this.block && $('<span>').addClass('insertanychar-block').append(
$('<a>').attr({
href: '#',
title: 'Show only this block'
}).text(block.name)
.on('click', block, this.onBlockClick)
.on('mousedown', Iac.stopPropagation)
)
)[0],
$('<span>').addClass('insertanychar-hex').text(hex)[0]
]);
}
return item.setLabel($label).setTitle(
compact ? `${data.name} (${hex})\n${block.name}` : null
);
}
showBlock(block) {
this.prevBlock = this.block;
this.block = block;
this.chars.prevBlock.toggle(true);
this.chars.$blockLink.attr(
'href',
`https://www.unicode.org/charts/PDF/U${Iac.getHex(block.start)}.pdf`
).text(block.name).show();
this.chars.reset.toggle(true);
this.chars.nextBlock.toggle(true);
if (this.chars.filterToggle.getValue()) {
this.chars.filterToggle.setValue(false);
} else {
this.filterChars();
}
}
showAdjacentBlock(prev) {
let i = this.blocks.blocks.indexOf(this.block), block;
do {
if (prev) {
i--;
} else {
i++;
}
block = this.blocks.blocks.at(i) || this.blocks.blocks[0];
} while (!block.entries.length);
this.showBlock(block);
}
resetBlock() {
this.prevBlock = this.block;
this.block = null;
this.chars.prevBlock.toggle(false);
this.chars.$blockLink.hide();
this.chars.nextBlock.toggle(false);
this.chars.reset.toggle(false);
this.filterChars();
}
filterBlocks = () => {
let prevSearch = this.blocks.search;
this.blocks.search = new Search(
this.blocks.filter.getValue(),
this.blocks.filterToggle.getValue()
);
if (this.blocks.search.compare(prevSearch)) return;
let items = this.blocks.select.getItems(), firstVisible;
if (prevSearch?.valid && !this.blocks.search.valid) {
firstVisible = this.getFirstVisible('chars', items);
}
let filtered = this.blocks.search?.valid;
let toShow = this.blocks.items.filter(item => !filtered || (
this.blocks.search.test(item.getData().name.toUpperCase())
));
this.blocks.select.removeItems(items).addItems(toShow);
this.blocks.$notice.text(`${
filtered ? `${toShow.length.toLocaleString('en')} / ` : ''
}${this.blocks.count.toLocaleString('en')} blocks`);
if (toShow.includes(firstVisible)) {
this.scroll('blocks', firstVisible, {
alignToTop: true,
animate: false
});
} else {
this.blocks.tab.$element[0].scrollTop = 0;
}
};
getFirstVisible(side, items) {
let y = this[side].tab.$element[0].scrollTop;
return items.find(item => item.$element[0].offsetTop >= y);
}
getEdge(side, up) {
return this[side].select.getItems().at(up ? 0 : -1);
}
getAdjacent(side, up) {
let items = this[side].select.getItems();
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
return items[items.indexOf(highlighted) + (up ? -1 : 1)];
}
let y = this[side].tab.$element[0].scrollTop;
if (up) {
return items.findLast(item => item.$element[0].offsetTop < y);
}
y += this[side].tab.$element[0].clientHeight;
return items.find(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight > y
));
}
getPageAway(side, up) {
let items = this[side].select.getItems();
let y = this[side].tab.$element[0].scrollTop +
this[side].tab.$element[0].clientHeight * (up ? -1 : 2);
if (up) {
return items.find(item => item.$element[0].offsetTop >= y);
}
return items.findLast(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight < y
));
}
scroll(side, item, options = {}) {
if (!item) return;
options.padding = { top: this[side].$notice[0].offsetHeight };
item.scrollElementIntoView(options);
}
onFilterToggle(side, on) {
this[this[side].filterMethod]();
if (on && !OO.ui.isMobile()) {
setTimeout(() => {
this[side].filter.focus();
});
}
}
onBlockClick = e => {
e.preventDefault();
this.showBlock(e.data);
};
static stopPropagation(e) {
e.stopPropagation();
}
static getHex(v) {
return v.toString(16).toUpperCase().padStart(4, '0');
}
// https://www.unicode.org/versions/latest/core-spec/chapter-3/
static hangul = {
leads: [
'G', 'GG', 'N', 'D', 'DD', 'R', 'M', 'B', 'BB', 'S', 'SS', '',
'J', 'JJ', 'C', 'K', 'T', 'P', 'H'
],
vowels: [
'A', 'AE', 'YA', 'YAE', 'EO', 'E', 'YEO', 'YE', 'O', 'WA',
'WAE', 'OE', 'YO', 'U', 'WEO', 'WE', 'WI', 'YU', 'EU', 'YI', 'I'
],
trails: [
'', 'G', 'GG', 'GS', 'N', 'NJ', 'NH', 'D', 'L', 'LG', 'LM',
'LB', 'LS', 'LT', 'LP', 'LH', 'M', 'B', 'BS', 'S', 'SS', 'NG',
'J', 'C', 'K', 'T', 'P', 'H'
]
};
static getCharName(entry, v, appendHex) {
if (entry.isHangul) {
let i = v - entry.start;
return entry.name + ' ' + Iac.hangul.leads[Math.floor(i / 588)] +
Iac.hangul.vowels[Math.floor((i % 588) / 28)] +
Iac.hangul.trails[i % 28];
}
if (appendHex) {
return entry.name + ' ' + Iac.getHex(v);
}
return entry.name;
}
static renderChar(v, combining) {
return `${combining ? '◌' : ''}${
String.fromCodePoint(v)
}${combining === 2 ? '◌' : ''}`;
}
static inBlock(v, block) {
return v >= block.start && v <= block.end;
}
static isRange(entry) {
return !!entry && Object.hasOwn(entry, 'start');
}
}
class Search {
constructor(query, valid) {
let terms = [], negTerms = new Set();
if (valid) {
query.toUpperCase().replace(/[^-\s\dA-Z]/g, '').split(/\s+/).forEach(s => {
if (s.startsWith('-')) {
if (s !== '-') {
negTerms.add(s.slice(1));
}
} else {
terms.push(...s.split('-').filter(Boolean));
}
});
}
this.negTerms = [...negTerms].sort();
this.terms = terms.filter(t => (
!this.negTerms.some(nt => t.includes(nt))
)).sort();
this.termCount = terms.length;
this.valid = !!(this.termCount || this.negTerms.length);
}
test(s) {
if (this.negTerms.some(term => s.includes(term))) return;
let words = s.split(/[ -]/);
let exactCount = 0, initialCount = 0, partialCount = 0;
this.terms.filter(term => {
let i = words.indexOf(term);
if (i === -1) {
return true;
}
words.splice(i, 1);
exactCount++;
}).filter(term => {
let i = words.findIndex(word => word.startsWith(term));
if (i === -1) {
return true;
}
words.splice(i, 1);
initialCount++;
}).forEach(term => {
let i = words.findIndex(word => word.includes(term));
if (i === -1) return;
words.splice(i, 1);
partialCount++;
});
switch (this.termCount) {
case exactCount:
return 1;
case exactCount && initialCount && exactCount + initialCount + partialCount:
return 2;
case exactCount && exactCount + partialCount:
return 3;
case initialCount:
return 4;
case initialCount && initialCount + partialCount:
return 5;
case partialCount:
return 6;
}
}
testHex(s) {
return !this.negTerms.some(term => s.includes(term)) &&
this.terms.every(term => s.includes(term) || /^[\dA-F]{1,6}$/.test(term));
}
compare(search) {
if (!search) {
return !this.valid;
}
return search.valid === this.valid &&
String(search.terms) === String(this.terms) &&
String(search.negTerms) === String(this.negTerms);
}
}
let css = mw.loader.addStyleTag(`.wikiEditor-ui:not(.insertanychar-available) .tool[rel="insertanychar"],
.wikiEditor-ui.insertanychar-enabled .tool[rel="realtimepreview"] {
display: none;
}`);
let init = async context => {
if (clicked) {
if (iac) {
iac.context = context;
iac.toggle();
}
return;
}
clicked = true;
css.textContent += `
.insertanychar-loading {
padding: 8px !important;
}
.insertanychar-bar {
display: flex;
}
.insertanychar-bar > .oo-ui-widget {
margin: 0 !important;
}
.insertanychar-history {
display: flex;
overflow: auto;
}
.insertanychar-history > .oo-ui-menuOptionWidget {
padding: 0;
margin: 2px;
}
.insertanychar-history > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label,
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
overflow: visible;
}
.insertanychar > .oo-ui-indexLayout {
flex-grow: 1;
position: relative;
}
.insertanychar .oo-ui-tabSelectWidget {
display: flex;
justify-content: space-evenly;
}
.insertanychar .oo-ui-tabOptionWidget {
margin-inline: 0;
width: 45%;
text-align: center;
}
.insertanychar .oo-ui-tabPanelLayout {
padding: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget {
padding-inline: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
display: flex;
align-items: center;
}
.insertanychar-char {
display: inline-block;
font-size: 200%;
min-width: 1.5em;
min-height: 1.5em;
line-height: 1.5;
text-align: center;
white-space: pre;
font-family: 'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif,'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif;
}
.insertanychar-sans .insertanychar-char {
font-family: 'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif,'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif;
}
.insertanychar-char::after {
content: var(--insertanychar-vs);
}
.insertanychar-history .insertanychar-char {
font-size: 150%;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-char {
margin: 0 2px;
}
.insertanychar-name {
flex-grow: 1;
white-space: normal !important;
word-break: break-word;
}
.insertanychar-block,
.insertanychar-info {
display: block;
}
.insertanychar-block > a,
.insertanychar-block > a:visited,
.insertanychar-info,
.insertanychar-hex,
.insertanychar-num {
font-size: 85%;
color: var(--color-subtle, #54595d);
}
.insertanychar-hex,
.insertanychar-num {
margin: 0 4px;
}
.insertanychar-compact {
display: flex;
flex-wrap: wrap;
align-content: start;
align-items: center;
}
.insertanychar-compact > .oo-ui-menuOptionWidget {
padding: 0;
margin: 4px 6px;
}
.insertanychar-compact .insertanychar-name,
.insertanychar-compact .insertanychar-hex {
display: none;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-loader {
padding-inline: 8px;
}
.insertanychar .oo-ui-toggleButtonWidget:not(.oo-ui-toggleWidget-on) + .oo-ui-textInputWidget-type-search {
visibility: hidden;
}
.insertanychar-notice {
padding: 2px 6px;
display: flex;
align-items: center;
position: sticky;
top: 0;
z-index: 1;
background-color: var(--background-color-base, #fff);
border-bottom: 1px solid var(--border-color-subtle, #c8ccd1);
}
.insertanychar-notice a {
font-weight: bold;
}`;
await mw.loader.using([
'oojs-ui-core', 'jquery.textSelection', 'ext.wikiEditor.realtimepreview',
'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-editing-styling', 'oojs-ui.styles.icons-layout'
]);
iac = new Iac(context).toggle(true);
window.iac = iac;
let { source } = await $.get({
url: '//en.wiktionary.org/w/rest.php/v1/revision/92711675',
timeout: 30000
});
let blocks = [...source.matchAll(/^([\dA-F]+)\.\.([\dA-F]+); (.+)$/gm)]
.map(([, start, end, name]) => ({
start: parseInt(start, 16),
end: parseInt(end, 16),
name,
entries: []
}));
let blockIdx = 0;
for (let rev of ['92711695', '92711699']) {
// eslint-disable-next-line no-shadow
let { source } = await $.get({
url: `//en.wiktionary.org/w/rest.php/v1/revision/${rev}`,
timeout: 30000
});
for (let [, hex, name, cat, combClass, oldName] of source.matchAll(
/^([^;]+);([^;]*);([^;]*);([^;]*);(?:[^;]*;){6}([^;]*)/gm
)) {
let v = parseInt(hex, 16);
while (v > blocks[blockIdx].end) {
blockIdx++;
}
let block = blocks[blockIdx];
if (name?.startsWith('<')) {
if (name === '<control>') {
name = oldName;
} else {
if (block.entries.length) {
block.entries[0].end = v;
} else {
let blockName = name.match(/[^,<>]+/)?.[0].toUpperCase();
if (!blockName || blockName.endsWith(' SURROGATE')) continue;
let entry = { start: v, name: blockName };
if (blockName === 'HANGUL SYLLABLE') {
entry.isHangul = true;
}
block.entries.push(entry);
block.sample = { v, name: Iac.getCharName(entry, v) };
}
continue;
}
}
if (!name) continue;
let entry = { v, name };
if (cat === 'Mn') {
entry.combining = 1 + ['233', '234'].includes(combClass);
}
block.entries.push(entry);
if (!block.sample && !['C', 'Z'].includes(cat[0])) {
block.sample = entry;
}
}
}
iac.ready(blocks);
};
mw.hook('wikiEditor.toolbarReady').add(async $textarea => {
$textarea.closest('.wikiEditor-ui').toggleClass(
'insertanychar-available',
mw.config.get('wgPageContentModel') !== 'wikitext' || (
await mw.loader.using('user.options'),
!Number(mw.user.options.get('wikieditor-realtimepreview'))
)
);
$textarea.wikiEditor('addToToolbar', {
section: 'secondary',
group: 'default',
tools: {
insertanychar: {
label: 'InsertAnyChar',
type: 'toggle',
oouiIcon: 'specialCharacter',
action: { type: 'callback', execute: init }
}
}
});
});
mw.hook('ext.WikiEditor.realtimepreview.disable').add(rtp => {
rtp.context.$ui.addClass('insertanychar-available');
});
mw.hook('ext.WikiEditor.realtimepreview.enable').add(rtp => {
rtp.context.$ui.removeClass('insertanychar-available');
});
}());
r34u6pky31vehgox8l6g9ke68yf4bbl
783647
783646
2026-11-09T03:37:43Z
Nardog
40946
783647
javascript
text/javascript
window.iac?.toggle(false);
$('.insertanychar, .tool[rel="insertanychar"]').remove();
$('head > style').filter(function () {
return this.textContent.includes('.insertanychar');
}).remove();
['edit', 'submit'].includes(mw.config.get('wgAction')) &&
(function () {
let clicked, iac;
class Iac {
constructor(context) {
this.context = context;
this.history = new OO.ui.SelectWidget({
classes: ['insertanychar-history']
}).on('choose', item => {
this.history.unselectItem(item);
this.insert(item.getData());
});
this.historyToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'history',
invisibleLabel: true,
label: 'History',
value: true
}).toggle().connect(this.history, { change: 'toggle' });
this.chars = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.chars.select.unselectItem(item);
if (this.chars.loaders.includes(item)) {
this.renderChars(item);
return;
}
this.insert(item.getData());
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter characters',
value: true
}).connect(this, { change: ['onFilterToggle', 'chars'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter characters'
}).on('change', OO.ui.debounce(this.filterChars, 500)),
filterMethod: 'filterChars',
prevBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'previous',
invisibleLabel: true,
label: 'Previous block'
}).toggle().connect(this, { click: ['showAdjacentBlock', true] }),
$blockLink: $('<a>').attr({
class: 'external',
rel: 'nofollow',
title: 'Open Unicode PDF',
target: '_blank'
}),
$info: $('<span>').addClass('insertanychar-info'),
nextBlock: new OO.ui.ButtonWidget({
framed: false,
icon: 'next',
invisibleLabel: true,
label: 'Next block'
}).toggle().connect(this, { click: 'showAdjacentBlock' }),
reset: new OO.ui.ButtonWidget({
flags: ['destructive'],
framed: false,
label: 'Reset',
title: 'Show all characters'
}).toggle().connect(this, { click: 'resetBlock' })
};
this.compactToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'viewCompact',
invisibleLabel: true,
label: 'Compact view'
}).on('change', on => {
let items = this.chars.select.getItems();
let firstVisible = this.getFirstVisible('chars', items);
this.chars.select.$element.toggleClass('insertanychar-compact', on);
let lang = this.options.lang.getValue();
items.forEach(item => {
if (!this.chars.loaders.includes(item)) {
this.renderChar(item, on, lang);
}
});
this.scroll('chars', firstVisible, {
alignToTop: true,
animate: false
});
});
this.chars.$notice = $('<div>').addClass('insertanychar-notice').append(
this.chars.prevBlock.$element,
$('<span>').addClass('insertanychar-name').append(
this.chars.$blockLink,
this.chars.$info
),
this.chars.nextBlock.$element,
this.chars.reset.$element
);
this.chars.tab = new OO.ui.TabPanelLayout('chars', {
$content: $([
this.chars.$notice[0],
this.chars.select.$element[0]
]),
label: 'Characters'
}).connect(this.chars.filterToggle, { active: 'toggle' })
.connect(this.chars.filter, { active: 'toggle' })
.connect(this.compactToggle, { active: 'toggle' });
this.blocks = {
select: new OO.ui.SelectWidget({
classes: ['insertanychar-select']
}).on('choose', item => {
this.blocks.select.unselectItem(item);
this.showBlock(item.getData());
this.index.setTabPanel('chars');
}),
filterToggle: new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'funnel',
invisibleLabel: true,
label: 'Filter blocks',
value: true
}).toggle().connect(this, { change: ['onFilterToggle', 'blocks'] }),
filter: new OO.ui.SearchInputWidget({
autocomplete: false,
icon: '',
title: 'Filter blocks'
}).toggle().on('change', OO.ui.debounce(this.filterBlocks, 250)),
filterMethod: 'filterBlocks',
$notice: $('<div>').addClass('insertanychar-notice insertanychar-info')
};
this.blocks.tab = new OO.ui.TabPanelLayout('blocks', {
$content: $([
this.blocks.$notice[0],
this.blocks.select.$element[0]
]),
label: 'Blocks'
}).connect(this.blocks.filterToggle, { active: 'toggle' })
.connect(this.blocks.filter, { active: 'toggle' });
this.index = new OO.ui.IndexLayout({
autoFocus: false,
framed: false
}).addTabPanels([this.chars.tab, this.blocks.tab]).on('set', tab => {
let side = tab.getName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
});
this.options = {
font: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: 0,
label: 'Serif first'
}),
new OO.ui.MenuOptionWidget({
data: 1,
label: 'Sans-serif first'
})
]
}
}),
lang: new OO.ui.ComboBoxInputWidget({
$overlay: true,
options: [
{ data: '', label: '(Default)' },
{ data: 'ja' },
{ data: 'ko' },
{ data: 'vi' },
{ data: 'zh-Hans' },
{ data: 'zh-Hant' },
{ data: 'zh-Hant-HK' },
{ data: 'zh-Hant-TW' }
],
placeholder: '(Default)'
}).on('change', on => {
this.$pane.find('.insertanychar-char').attr('lang', on || null);
}),
vs: new OO.ui.DropdownWidget({
$overlay: true,
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: '',
label: 'None'
})
]
}
})
};
this.options.font.getMenu().selectItemByData(0).on('choose', item => {
this.$pane.toggleClass('insertanychar-sans', !!item.getData());
});
this.options.vs.getMenu().selectItemByData('').on('choose', item => {
let v = item.getData();
this.$pane.css(
'--insertanychar-vs',
v ? `'\\${v.toString(16)}'` : ''
);
});
this.options.panel = new OO.ui.PanelLayout({
$content: $([
new OO.ui.FieldLayout(this.options.font, {
label: 'Font stack:'
}).$element[0],
new OO.ui.FieldLayout(this.options.lang, {
label: 'Language:'
}).$element[0],
new OO.ui.FieldLayout(this.options.vs, {
label: 'Variation selector:'
}).$element[0]
]),
expanded: false,
padded: true
}).toggle();
this.optionsToggle = new OO.ui.ToggleButtonWidget({
framed: false,
icon: 'textStyle',
invisibleLabel: true,
label: 'Preview options'
}).connect(this.options.panel, { change: 'toggle' });
this.chars.loaders = [
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
}),
new OO.ui.MenuOptionWidget({
classes: ['insertanychar-loader'],
label: 'Load more'
})
];
this.chars.observer = new IntersectionObserver(entries => {
if (!entries[0].isIntersecting) return;
this.renderChars(this.chars.loaders.find(loader => (
loader.$element.is(entries[0].target))
));
}, { threshold: 0.75 });
this.chars.loaders.forEach(loader => {
this.chars.observer.observe(loader.$element[0]);
});
this.twoPaneLayout = new mw.loader.moduleRegistry['ext.wikiEditor.realtimepreview']
.packageExports['realtimepreview/TwoPaneLayout.js']();
this.$pane = this.twoPaneLayout.getPane2()
.addClass('insertanychar insertanychar-loading')
.text('Loading...');
}
ready(blocks) {
this.chars.count = 0;
this.blocks.blocks = blocks;
this.blocks.count = blocks.length;
let vs = [];
this.blocks.items = blocks.map((block, i) => {
if (block.name.startsWith('Variation Selectors')) {
vs.push(...block.entries);
}
let { sample } = block;
delete block.sample;
let $sample = $('<span>').addClass('insertanychar-char');
if (sample) {
$sample.text(Iac.renderChar(sample.v, sample.combining));
}
block.count = Iac.isRange(block.entries[0])
? block.entries[0].end - block.entries[0].start
: block.entries.length;
this.chars.count += block.count;
return new OO.ui.MenuOptionWidget({
data: block,
disabled: !block.count,
label: $([
$sample[0],
$('<span>').addClass('insertanychar-name').text(
block.name
).append(
$('<span>').addClass('insertanychar-info').text(
`${block.count.toLocaleString('en')} characters`
)
)[0],
$('<span>').addClass('insertanychar-num').text(i + 1)[0]
])
});
});
this.blocks.select.addItems(this.blocks.items);
this.options.vs.getMenu().addItems(vs.map(entry => (
new OO.ui.MenuOptionWidget({
data: entry.v,
label: `${entry.name.slice(19)} (${Iac.getHex(entry.v)})`
})
)));
this.blocks.$notice
.text(`${this.blocks.count.toLocaleString('en')} blocks`);
this.$pane.empty().removeClass('insertanychar-loading').append(
this.history.$element,
this.index.$element,
this.options.panel.$element,
$('<div>').addClass('insertanychar-bar').append(
this.historyToggle.$element,
this.chars.filterToggle.$element,
this.chars.filter.$element,
this.blocks.filterToggle.$element,
this.blocks.filter.$element,
this.optionsToggle.$element,
this.compactToggle.$element
)
).on('keydown', e => {
if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
let isEnter = e.which === 13;
let isUp = e.which === 38;
let isDown = e.which === 40;
let isHome = e.which === 36;
let isEnd = e.which === 35;
let isPgUp = e.which === 33;
let isPgDown = e.which === 34;
if (!(isEnter || isUp || isDown || isHome || isEnd || isPgUp || isPgDown)) return;
e.preventDefault();
let side = this.index.getCurrentTabPanelName();
if (isEnter) {
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
this[side].select.chooseItem(highlighted);
}
return;
}
let item = (isUp || isDown) && this.getAdjacent(side, isUp) ||
(isPgUp || isPgDown) && this.getPageAway(side, isPgUp) ||
this.getEdge(side, isDown || isPgUp || isHome);
if (item) {
this[side].select.highlightItem(item);
this.scroll(side, item);
}
});
this.filterChars(true);
if (!OO.ui.isMobile()) {
this.chars.filter.focus();
}
}
toggle(visible = !this.visible) {
let $uiText = this.context.$ui
.toggleClass('insertanychar-enabled', visible)
.find('.wikiEditor-ui-text');
if (visible) {
$uiText.before(this.twoPaneLayout.$element);
this.twoPaneLayout.setPane1($uiText);
this.twoPaneLayout.$element.show();
this.twoPaneLayout.$element.css('height', $uiText.height() + 'px');
$uiText.css('height', '100%');
let side = this.index.getCurrentTabPanelName();
if (this[side].filterToggle.getValue() && !OO.ui.isMobile()) {
this[side].filter.focus();
}
} else {
$uiText.css('height', this.twoPaneLayout.$element.height() + 'px');
this.twoPaneLayout.$element.after($uiText).hide();
}
this.visible = visible;
this.context.$ui.find('.tool[rel="insertanychar"]').data('ooui')
.setFlags({ progressive: visible });
return this;
}
insert(data) {
this.context.$textarea.textSelection('encapsulateSelection', {
peri: String.fromCodePoint(data.v),
selectPeri: false
});
let historyItem = this.history.getItems().find(item => (
item.getData().v === data.v
)) || this.renderChar(data, true);
this.history.addItems([historyItem], 0);
this.historyToggle.toggle(true);
}
filterChars = first => {
let prevSearch = this.chars.search;
this.chars.search = new Search(
this.chars.filter.getValue(),
this.chars.filterToggle.getValue()
);
if (!first && this.block === this.prevBlock &&
this.chars.search.compare(prevSearch)
) {
return;
}
this.prevBlock = this.block;
this.chars.entries = this.block
? this.block.entries
: this.blocks.blocks.flatMap(block => block.entries);
if (!this.chars.search.valid) {
this.chars.$info.text(`${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars(null, !this.block);
return;
}
let matches = [[], [], [], [], [], []];
this.chars.entries.forEach(entry => {
if (!Iac.isRange(entry)) {
let result = this.chars.search.test(entry.name);
if (result) {
matches[result - 1].push(entry);
}
return;
}
if (!entry.isHangul && !this.chars.search.testHex(entry.name)) return;
for (let v = entry.start; v <= entry.end; v++) {
let nameToTest = Iac.getCharName(entry, v, true);
let result = this.chars.search.test(nameToTest);
if (result) {
matches[result - 1].push({
v,
name: entry.isHangul ? nameToTest : entry.name
});
}
}
});
this.chars.entries = matches.flat();
this.chars.$info.text(`${
this.chars.entries.length.toLocaleString('en')
} / ${
(this.block?.count || this.chars.count).toLocaleString('en')
} characters`);
this.renderChars();
};
renderChars(loader, startMid) {
let compact = this.compactToggle.getValue();
let lang = this.options.lang.getValue();
let prevItems = this.chars.select.getItems();
let prevCharItems = prevItems
.filter(item => !this.chars.loaders.includes(item));
let i = 0, blockV;
if (startMid) {
let firstVisible = this.getFirstVisible('chars', prevCharItems);
if (firstVisible) {
let data = firstVisible.getData();
this.chars.entries.some((entry, j) => {
if (Iac.isRange(entry)) {
if (Iac.inBlock(data.v, entry)) {
i = j;
blockV = data.v;
return true;
}
} else if (entry.v === data.v) {
i = j;
return true;
}
});
}
}
let up = loader === this.chars.loaders[0];
let increment = up ? -1 : 1;
if (loader) {
this.chars.select.removeItems(this.chars.loaders);
let adjacent = prevCharItems.at(up ? 0 : -1);
if (adjacent) {
let data = adjacent.getData();
if (Iac.isRange(data.entry)) {
i = this.chars.entries.indexOf(data.entry);
blockV = data.v + increment;
} else {
i = this.chars.entries.indexOf(data.entry) + increment;
}
}
} else {
this.chars.select.removeItems(prevItems);
}
let toAdd = [], len = 0;
do {
let entry = this.chars.entries[i];
if (!entry) break;
if (!Iac.isRange(entry)) {
len = toAdd.push(this.renderChar({
v: entry.v,
entry
}, compact, lang));
i += increment;
continue;
}
if (typeof blockV === 'number') {
if (!Iac.inBlock(blockV, entry)) {
blockV = null;
i += increment;
continue;
}
} else {
blockV = entry[up ? 'end' : 'start'];
}
len = toAdd.push(this.renderChar({
v: blockV,
entry
}, compact, lang));
blockV += increment;
} while (len < 128);
if (loader) {
let toRemove = up
? prevCharItems.slice(384 - len)
: prevCharItems.slice(0, -384 + len);
this.chars.select.removeItems(toRemove);
}
if (up) {
this.chars.select.addItems(toAdd.reverse(), 0);
} else {
this.chars.select.addItems(toAdd);
}
let reached = {};
let items = this.chars.select.getItems();
if (items.length) {
[['start', 0], ['end', -1]].forEach(([pos, idx]) => {
let data = items.at(idx).getData();
reached[pos] = data.entry === this.chars.entries.at(idx) && (
!Iac.isRange(data.entry) || data.v === data.entry[pos]
);
});
} else {
reached.start = true;
reached.end = true;
}
if (!reached.start) {
this.chars.select.addItems([this.chars.loaders[0]], 0);
}
if (!reached.end) {
this.chars.select.addItems([this.chars.loaders[1]]);
}
if (loader) {
this.scroll('chars', toAdd.at(up ? -1 : 0), {
alignToTop: up,
animate: false
});
} else {
this.chars.tab.$element[0].scrollTop = 0;
}
}
renderChar(item, compact, lang = this.options.lang.getValue()) {
let data;
if ($.isPlainObject(item)) {
data = item;
item = new OO.ui.MenuOptionWidget({ data });
data.name = Iac.getCharName(data.entry, data.v).replace(
/ (?:AND|AS|AT|BY|FOR|FROM|IN|OF|ON|OR|THE|TO|WITH)(?= )/g,
s => s.toLowerCase()
).replace(
/\b(?!CJK\b)([A-Z])([A-Z]+)\b/g,
($0, $1, $2) => $1 + $2.toLowerCase()
);
} else {
data = item.getData();
}
let block = this.block ||
this.blocks.blocks.find(b => Iac.inBlock(data.v, b));
let hex = Iac.getHex(data.v);
let $label = item.getLabel() || $('<span>').attr({
class: 'insertanychar-char',
lang: lang
}).text(Iac.renderChar(data.v, data.entry.combining));
if (!compact && !$label.children().length) {
$label = $([
$label[0],
$('<span>').addClass('insertanychar-name').text(
data.name
).append(
!this.block && $('<span>').addClass('insertanychar-block').append(
$('<a>').attr({
href: '#',
title: 'Show only this block'
}).text(block.name)
.on('click', block, this.onBlockClick)
.on('mousedown', Iac.stopPropagation)
)
)[0],
$('<span>').addClass('insertanychar-hex').text(hex)[0]
]);
}
return item.setLabel($label).setTitle(
compact ? `${data.name} (${hex})\n${block.name}` : null
);
}
showBlock(block) {
this.prevBlock = this.block;
this.block = block;
this.chars.prevBlock.toggle(true);
this.chars.$blockLink.attr(
'href',
`https://www.unicode.org/charts/PDF/U${Iac.getHex(block.start)}.pdf`
).text(block.name).show();
this.chars.reset.toggle(true);
this.chars.nextBlock.toggle(true);
if (this.chars.filterToggle.getValue()) {
this.chars.filterToggle.setValue(false);
} else {
this.filterChars();
}
}
showAdjacentBlock(prev) {
let i = this.blocks.blocks.indexOf(this.block), block;
do {
i += prev ? -1 : 1;
block = this.blocks.blocks.at(i) || this.blocks.blocks[0];
} while (!block.entries.length);
this.showBlock(block);
}
resetBlock() {
this.prevBlock = this.block;
this.block = null;
this.chars.prevBlock.toggle(false);
this.chars.$blockLink.hide();
this.chars.nextBlock.toggle(false);
this.chars.reset.toggle(false);
this.filterChars();
}
filterBlocks = () => {
let prevSearch = this.blocks.search;
this.blocks.search = new Search(
this.blocks.filter.getValue(),
this.blocks.filterToggle.getValue()
);
if (this.blocks.search.compare(prevSearch)) return;
let items = this.blocks.select.getItems(), firstVisible;
if (prevSearch?.valid && !this.blocks.search.valid) {
firstVisible = this.getFirstVisible('chars', items);
}
let filtered = this.blocks.search?.valid;
let toShow = this.blocks.items.filter(item => !filtered || (
this.blocks.search.test(item.getData().name.toUpperCase())
));
this.blocks.select.removeItems(items).addItems(toShow);
this.blocks.$notice.text(`${
filtered ? `${toShow.length.toLocaleString('en')} / ` : ''
}${this.blocks.count.toLocaleString('en')} blocks`);
if (toShow.includes(firstVisible)) {
this.scroll('blocks', firstVisible, {
alignToTop: true,
animate: false
});
} else {
this.blocks.tab.$element[0].scrollTop = 0;
}
};
getFirstVisible(side, items) {
let y = this[side].tab.$element[0].scrollTop;
return items.find(item => item.$element[0].offsetTop >= y);
}
getEdge(side, up) {
return this[side].select.getItems().at(up ? 0 : -1);
}
getAdjacent(side, up) {
let items = this[side].select.getItems();
let highlighted = this[side].select.findHighlightedItem();
if (highlighted) {
return items[items.indexOf(highlighted) + (up ? -1 : 1)];
}
let y = this[side].tab.$element[0].scrollTop;
if (up) {
return items.findLast(item => item.$element[0].offsetTop < y);
}
y += this[side].tab.$element[0].clientHeight;
return items.find(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight > y
));
}
getPageAway(side, up) {
let items = this[side].select.getItems();
let y = this[side].tab.$element[0].scrollTop +
this[side].tab.$element[0].clientHeight * (up ? -1 : 2);
if (up) {
return items.find(item => item.$element[0].offsetTop >= y);
}
return items.findLast(item => (
item.$element[0].offsetTop + item.$element[0].offsetHeight < y
));
}
scroll(side, item, options = {}) {
if (!item) return;
options.padding = { top: this[side].$notice[0].offsetHeight };
item.scrollElementIntoView(options);
}
onFilterToggle(side, on) {
this[this[side].filterMethod]();
if (on && !OO.ui.isMobile()) {
setTimeout(() => {
this[side].filter.focus();
});
}
}
onBlockClick = e => {
e.preventDefault();
this.showBlock(e.data);
};
static stopPropagation(e) {
e.stopPropagation();
}
static getHex(v) {
return v.toString(16).toUpperCase().padStart(4, '0');
}
// https://www.unicode.org/versions/latest/core-spec/chapter-3/
static hangul = {
leads: [
'G', 'GG', 'N', 'D', 'DD', 'R', 'M', 'B', 'BB', 'S', 'SS', '',
'J', 'JJ', 'C', 'K', 'T', 'P', 'H'
],
vowels: [
'A', 'AE', 'YA', 'YAE', 'EO', 'E', 'YEO', 'YE', 'O', 'WA',
'WAE', 'OE', 'YO', 'U', 'WEO', 'WE', 'WI', 'YU', 'EU', 'YI', 'I'
],
trails: [
'', 'G', 'GG', 'GS', 'N', 'NJ', 'NH', 'D', 'L', 'LG', 'LM',
'LB', 'LS', 'LT', 'LP', 'LH', 'M', 'B', 'BS', 'S', 'SS', 'NG',
'J', 'C', 'K', 'T', 'P', 'H'
]
};
static getCharName(entry, v, appendHex) {
if (entry.isHangul) {
let i = v - entry.start;
return entry.name + ' ' + Iac.hangul.leads[Math.floor(i / 588)] +
Iac.hangul.vowels[Math.floor((i % 588) / 28)] +
Iac.hangul.trails[i % 28];
}
if (appendHex) {
return entry.name + ' ' + Iac.getHex(v);
}
return entry.name;
}
static renderChar(v, combining) {
return `${combining ? '◌' : ''}${
String.fromCodePoint(v)
}${combining === 2 ? '◌' : ''}`;
}
static inBlock(v, block) {
return v >= block.start && v <= block.end;
}
static isRange(entry) {
return !!entry && Object.hasOwn(entry, 'start');
}
}
class Search {
constructor(query, valid) {
let terms = [], negTerms = new Set();
if (valid) {
query.toUpperCase().replace(/[^-\s\dA-Z]/g, '').split(/\s+/).forEach(s => {
if (s.startsWith('-')) {
if (s !== '-') {
negTerms.add(s.slice(1));
}
} else {
terms.push(...s.split('-').filter(Boolean));
}
});
}
this.negTerms = [...negTerms].sort();
this.terms = terms.filter(t => (
!this.negTerms.some(nt => t.includes(nt))
)).sort();
this.termCount = terms.length;
this.valid = !!(this.termCount || this.negTerms.length);
}
test(s) {
if (this.negTerms.some(term => s.includes(term))) return;
let words = s.split(/[ -]/);
let exactCount = 0, initialCount = 0, partialCount = 0;
this.terms.filter(term => {
let i = words.indexOf(term);
if (i === -1) {
return true;
}
words.splice(i, 1);
exactCount++;
}).filter(term => {
let i = words.findIndex(word => word.startsWith(term));
if (i === -1) {
return true;
}
words.splice(i, 1);
initialCount++;
}).forEach(term => {
let i = words.findIndex(word => word.includes(term));
if (i === -1) return;
words.splice(i, 1);
partialCount++;
});
switch (this.termCount) {
case exactCount:
return 1;
case exactCount && initialCount && exactCount + initialCount + partialCount:
return 2;
case exactCount && exactCount + partialCount:
return 3;
case initialCount:
return 4;
case initialCount && initialCount + partialCount:
return 5;
case partialCount:
return 6;
}
}
testHex(s) {
return !this.negTerms.some(term => s.includes(term)) &&
this.terms.every(term => s.includes(term) || /^[\dA-F]{1,6}$/.test(term));
}
compare(search) {
if (!search) {
return !this.valid;
}
return search.valid === this.valid &&
String(search.terms) === String(this.terms) &&
String(search.negTerms) === String(this.negTerms);
}
}
let css = mw.loader.addStyleTag(`.wikiEditor-ui:not(.insertanychar-available) .tool[rel="insertanychar"],
.wikiEditor-ui.insertanychar-enabled .tool[rel="realtimepreview"] {
display: none;
}`);
let init = async context => {
if (clicked) {
if (iac) {
iac.context = context;
iac.toggle();
}
return;
}
clicked = true;
css.textContent += `
.insertanychar-loading {
padding: 8px !important;
}
.insertanychar-bar {
display: flex;
}
.insertanychar-bar > .oo-ui-widget {
margin: 0 !important;
}
.insertanychar-history {
display: flex;
overflow: auto;
}
.insertanychar-history > .oo-ui-menuOptionWidget {
padding: 0;
margin: 2px;
}
.insertanychar-history > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label,
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
overflow: visible;
}
.insertanychar > .oo-ui-indexLayout {
flex-grow: 1;
position: relative;
}
.insertanychar .oo-ui-tabSelectWidget {
display: flex;
justify-content: space-evenly;
}
.insertanychar .oo-ui-tabOptionWidget {
margin-inline: 0;
width: 45%;
text-align: center;
}
.insertanychar .oo-ui-tabPanelLayout {
padding: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget {
padding-inline: 0;
}
.insertanychar-select > .oo-ui-menuOptionWidget > .oo-ui-labelElement-label {
display: flex;
align-items: center;
}
.insertanychar-char {
display: inline-block;
font-size: 200%;
min-width: 1.5em;
min-height: 1.5em;
line-height: 1.5;
text-align: center;
white-space: pre;
font-family: 'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif,'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif;
}
.insertanychar-sans .insertanychar-char {
font-family: 'Andika','Noto Sans','Liberation Sans','Roboto','Bitstream Cyberbit','Code2000','Lucida Grande','Lucida Sans Unicode',sans-serif,'Charis','Charis SIL','Doulos SIL','Gentium','Gentium Plus','Noto Serif','Liberation Serif','Roboto Serif',serif;
}
.insertanychar-char::after {
content: var(--insertanychar-vs);
}
.insertanychar-history .insertanychar-char {
font-size: 150%;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-char {
margin: 0 2px;
}
.insertanychar-name {
flex-grow: 1;
white-space: normal !important;
word-break: break-word;
}
.insertanychar-block,
.insertanychar-info {
display: block;
}
.insertanychar-block > a,
.insertanychar-block > a:visited,
.insertanychar-info,
.insertanychar-hex,
.insertanychar-num {
font-size: 85%;
color: var(--color-subtle, #54595d);
}
.insertanychar-hex,
.insertanychar-num {
margin: 0 4px;
}
.insertanychar-compact {
display: flex;
flex-wrap: wrap;
align-content: start;
align-items: center;
}
.insertanychar-compact > .oo-ui-menuOptionWidget {
padding: 0;
margin: 4px 6px;
}
.insertanychar-compact .insertanychar-name,
.insertanychar-compact .insertanychar-hex {
display: none;
}
.insertanychar-select:not(.insertanychar-compact) .insertanychar-loader {
padding-inline: 8px;
}
.insertanychar .oo-ui-toggleButtonWidget:not(.oo-ui-toggleWidget-on) + .oo-ui-textInputWidget-type-search {
visibility: hidden;
}
.insertanychar-notice {
padding: 2px 6px;
display: flex;
align-items: center;
position: sticky;
top: 0;
z-index: 1;
background-color: var(--background-color-base, #fff);
border-bottom: 1px solid var(--border-color-subtle, #c8ccd1);
}
.insertanychar-notice a {
font-weight: bold;
}`;
await mw.loader.using([
'oojs-ui-core', 'jquery.textSelection', 'ext.wikiEditor.realtimepreview',
'oojs-ui.styles.icons-movement', 'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-editing-styling', 'oojs-ui.styles.icons-layout'
]);
iac = new Iac(context).toggle(true);
window.iac = iac;
let { source } = await $.get({
url: '//en.wiktionary.org/w/rest.php/v1/revision/92711675',
timeout: 30000
});
let blocks = [...source.matchAll(/^([\dA-F]+)\.\.([\dA-F]+); (.+)$/gm)]
.map(([, start, end, name]) => ({
start: parseInt(start, 16),
end: parseInt(end, 16),
name,
entries: []
}));
let blockIdx = 0;
for (let rev of ['92711695', '92711699']) {
// eslint-disable-next-line no-shadow
let { source } = await $.get({
url: `//en.wiktionary.org/w/rest.php/v1/revision/${rev}`,
timeout: 30000
});
for (let [, hex, name, cat, combClass, oldName] of source.matchAll(
/^([^;]+);([^;]*);([^;]*);([^;]*);(?:[^;]*;){6}([^;]*)/gm
)) {
let v = parseInt(hex, 16);
while (v > blocks[blockIdx].end) {
blockIdx++;
}
let block = blocks[blockIdx];
if (name?.startsWith('<')) {
if (name === '<control>') {
name = oldName;
} else {
if (block.entries.length) {
block.entries[0].end = v;
} else {
let blockName = name.match(/[^,<>]+/)?.[0].toUpperCase();
if (!blockName || blockName.endsWith(' SURROGATE')) continue;
let entry = { start: v, name: blockName };
if (blockName === 'HANGUL SYLLABLE') {
entry.isHangul = true;
}
block.entries.push(entry);
block.sample = { v, name: Iac.getCharName(entry, v) };
}
continue;
}
}
if (!name) continue;
let entry = { v, name };
if (cat === 'Mn') {
entry.combining = 1 + ['233', '234'].includes(combClass);
}
block.entries.push(entry);
if (!block.sample && !['C', 'Z'].includes(cat[0])) {
block.sample = entry;
}
}
}
iac.ready(blocks);
};
mw.hook('wikiEditor.toolbarReady').add(async $textarea => {
$textarea.closest('.wikiEditor-ui').toggleClass(
'insertanychar-available',
mw.config.get('wgPageContentModel') !== 'wikitext' || (
await mw.loader.using('user.options'),
!Number(mw.user.options.get('wikieditor-realtimepreview'))
)
);
$textarea.wikiEditor('addToToolbar', {
section: 'secondary',
group: 'default',
tools: {
insertanychar: {
label: 'InsertAnyChar',
type: 'toggle',
oouiIcon: 'specialCharacter',
action: { type: 'callback', execute: init }
}
}
});
});
mw.hook('ext.WikiEditor.realtimepreview.disable').add(rtp => {
rtp.context.$ui.addClass('insertanychar-available');
});
mw.hook('ext.WikiEditor.realtimepreview.enable').add(rtp => {
rtp.context.$ui.removeClass('insertanychar-available');
});
}());
k7tnumc78ux42dex1acycdf7bh5yxig
Bob Morley
0
171957
783569
782418
2026-11-08T20:49:02Z
DVRTed
55993
[[WP:LLMPRV|Presumptive removal of LLM-generated content]], see [[User:DVRTed/sandbox2]]. Please do not remove this tag without following [[WP:LLMPRVOBJ|the procedures for disputing presumptive removal of LLM-generated content]]. (using [[User:DVRTed/AINB-helper|AINB-helper]])
783569
wikitext
text/x-wiki
{{Prod llm/dated
| concern = [[WP:LLMPRV|Presumptive removal of LLM-generated content]], see [[User:DVRTed/sandbox2]]. Please do not remove this tag without following [[WP:LLMPRVOBJ|the procedures for disputing presumptive removal of LLM-generated content]].
| user =
| timestamp = 20261108204902
| help = off
}}
{{Prod llm/dated
| concern = [[WP:LLMPRV|Presumptive removal of LLM-generated content]], see [[User:DVRTed/sandbox2]]. Please do not remove this tag without following [[WP:LLMPRVOBJ|the procedures for disputing presumptive removal of LLM-generated content]].
| user =
| timestamp = 20261101215909
| help = off
}}
{{Prod llm/dated
| concern = [[WP:LLMPRV|Presumptive removal of LLM-generated content]], see [[Wikipedia:AI noticeboard#Order of the Knights of the Fiery Cross]]. Feel free to reinstate by following [[WP:LLMPRVOBJ|the procedures for disputing presumptive removal of LLM-generated content]].
| user =
| timestamp = 20260916141014
| help =
}}
gvikirwlnjq2g8hej33itzil0a85hhq
User:DVRTed/LLMPROD log
2
178451
783486
782419
2026-11-08T13:40:09Z
DVRTed
55993
Logging AI tag addition on [[Test Page 817876]] (using [[User:DVRTed/AINB-helper|AINB-helper]])
783486
wikitext
text/x-wiki
# [[:Testpage12]]: Added {{tl|AI-generated}} with [[special:diff/762233|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:37, 18 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|AI-generated}} with [[special:diff/762235|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:46, 18 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|Prod llm}} with [[special:diff/762237|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:46, 18 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762688|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:28, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762691|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:41, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762707|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:46, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762710|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:47, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762713|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:47, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762716|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:48, 21 September 2026 (UTC)
# [[:Wikipedia:AI noticeboard/2026-09-13 DVRTed]]: Added {{tl|AI-generated}} with [[special:diff/762720|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:50, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762772|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762774|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762776|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:User:DVRTed/sandbox2]]: Added {{tl|Prod llm}} with [[special:diff/762796|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 18:41, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762811|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:15, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762815|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:16, 21 September 2026 (UTC)
# [[:Wp/sas/Kabupatén Tangerang]]: Added {{tl|AI-generated}} with [[special:diff/762818|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:26, 21 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|Prod llm}} with [[special:diff/765089|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 23:29, 29 September 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/765241|this edit]] (relevant page: [[Wikipedia:AI noticeboard/DVRTed]]), 09:20, 30 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/765391|this edit]] (relevant page: [[Wikipedia:AI noticeboard/2026-09-13 DVRTed]]), 22:41, 30 September 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|Prod llm}} with [[special:diff/782058|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 23:29, 31 October 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|Prod llm}} with [[special:diff/782062|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 23:31, 31 October 2026 (UTC)
# [[:Bob Morley]]: Added {{tl|Prod llm}} with [[special:diff/782418|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 21:59, 1 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783485|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:40, 8 November 2026 (UTC)
5ozqwnopj97p3gcdlcwtbvcklon0e5w
783495
783486
2026-11-08T13:51:40Z
DVRTed
55993
Logging AI tag addition on [[Test Page 817876]] (using [[User:DVRTed/AINB-helper|AINB-helper]])
783495
wikitext
text/x-wiki
# [[:Testpage12]]: Added {{tl|AI-generated}} with [[special:diff/762233|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:37, 18 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|AI-generated}} with [[special:diff/762235|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:46, 18 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|Prod llm}} with [[special:diff/762237|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:46, 18 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762688|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:28, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762691|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:41, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762707|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:46, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762710|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:47, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762713|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:47, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762716|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:48, 21 September 2026 (UTC)
# [[:Wikipedia:AI noticeboard/2026-09-13 DVRTed]]: Added {{tl|AI-generated}} with [[special:diff/762720|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:50, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762772|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762774|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762776|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:User:DVRTed/sandbox2]]: Added {{tl|Prod llm}} with [[special:diff/762796|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 18:41, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762811|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:15, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762815|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:16, 21 September 2026 (UTC)
# [[:Wp/sas/Kabupatén Tangerang]]: Added {{tl|AI-generated}} with [[special:diff/762818|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:26, 21 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|Prod llm}} with [[special:diff/765089|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 23:29, 29 September 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/765241|this edit]] (relevant page: [[Wikipedia:AI noticeboard/DVRTed]]), 09:20, 30 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/765391|this edit]] (relevant page: [[Wikipedia:AI noticeboard/2026-09-13 DVRTed]]), 22:41, 30 September 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|Prod llm}} with [[special:diff/782058|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 23:29, 31 October 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|Prod llm}} with [[special:diff/782062|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 23:31, 31 October 2026 (UTC)
# [[:Bob Morley]]: Added {{tl|Prod llm}} with [[special:diff/782418|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 21:59, 1 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783485|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:40, 8 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783494|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:51, 8 November 2026 (UTC)
mor9sclcpem38bldm0vkj4px10q63d5
783499
783495
2026-11-08T13:53:06Z
DVRTed
55993
Logging AI tag addition on [[Test Page 817876]] (using [[User:DVRTed/AINB-helper|AINB-helper]])
783499
wikitext
text/x-wiki
# [[:Testpage12]]: Added {{tl|AI-generated}} with [[special:diff/762233|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:37, 18 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|AI-generated}} with [[special:diff/762235|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:46, 18 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|Prod llm}} with [[special:diff/762237|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:46, 18 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762688|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:28, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762691|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:41, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762707|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:46, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762710|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:47, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762713|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:47, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762716|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:48, 21 September 2026 (UTC)
# [[:Wikipedia:AI noticeboard/2026-09-13 DVRTed]]: Added {{tl|AI-generated}} with [[special:diff/762720|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:50, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762772|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762774|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762776|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:User:DVRTed/sandbox2]]: Added {{tl|Prod llm}} with [[special:diff/762796|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 18:41, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762811|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:15, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762815|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:16, 21 September 2026 (UTC)
# [[:Wp/sas/Kabupatén Tangerang]]: Added {{tl|AI-generated}} with [[special:diff/762818|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:26, 21 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|Prod llm}} with [[special:diff/765089|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 23:29, 29 September 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/765241|this edit]] (relevant page: [[Wikipedia:AI noticeboard/DVRTed]]), 09:20, 30 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/765391|this edit]] (relevant page: [[Wikipedia:AI noticeboard/2026-09-13 DVRTed]]), 22:41, 30 September 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|Prod llm}} with [[special:diff/782058|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 23:29, 31 October 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|Prod llm}} with [[special:diff/782062|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 23:31, 31 October 2026 (UTC)
# [[:Bob Morley]]: Added {{tl|Prod llm}} with [[special:diff/782418|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 21:59, 1 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783485|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:40, 8 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783494|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:51, 8 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783498|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:53, 8 November 2026 (UTC)
m47tia2r0q3pouew2xm5nnwb87ft6ve
783570
783499
2026-11-08T20:49:03Z
DVRTed
55993
Logging LLMPROD on [[Bob Morley]] (using [[User:DVRTed/AINB-helper|AINB-helper]])
783570
wikitext
text/x-wiki
# [[:Testpage12]]: Added {{tl|AI-generated}} with [[special:diff/762233|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:37, 18 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|AI-generated}} with [[special:diff/762235|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:46, 18 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|Prod llm}} with [[special:diff/762237|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:46, 18 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762688|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:28, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762691|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:41, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762707|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:46, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762710|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:47, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762713|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:47, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762716|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:48, 21 September 2026 (UTC)
# [[:Wikipedia:AI noticeboard/2026-09-13 DVRTed]]: Added {{tl|AI-generated}} with [[special:diff/762720|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:50, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762772|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762774|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762776|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:User:DVRTed/sandbox2]]: Added {{tl|Prod llm}} with [[special:diff/762796|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 18:41, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762811|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:15, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762815|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:16, 21 September 2026 (UTC)
# [[:Wp/sas/Kabupatén Tangerang]]: Added {{tl|AI-generated}} with [[special:diff/762818|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:26, 21 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|Prod llm}} with [[special:diff/765089|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 23:29, 29 September 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/765241|this edit]] (relevant page: [[Wikipedia:AI noticeboard/DVRTed]]), 09:20, 30 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/765391|this edit]] (relevant page: [[Wikipedia:AI noticeboard/2026-09-13 DVRTed]]), 22:41, 30 September 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|Prod llm}} with [[special:diff/782058|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 23:29, 31 October 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|Prod llm}} with [[special:diff/782062|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 23:31, 31 October 2026 (UTC)
# [[:Bob Morley]]: Added {{tl|Prod llm}} with [[special:diff/782418|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 21:59, 1 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783485|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:40, 8 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783494|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:51, 8 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783498|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:53, 8 November 2026 (UTC)
# [[:Bob Morley]]: Added {{tl|Prod llm}} with [[special:diff/783569|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 20:49, 8 November 2026 (UTC)
jrwk864rzk8nny37q9u4m7cbdykzlkf
783582
783570
2026-11-08T20:53:45Z
DVRTed
55993
Logging LLMPROD on [[Test Page 817876]] (using [[User:DVRTed/AINB-helper|AINB-helper]])
783582
wikitext
text/x-wiki
# [[:Testpage12]]: Added {{tl|AI-generated}} with [[special:diff/762233|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:37, 18 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|AI-generated}} with [[special:diff/762235|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:46, 18 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|Prod llm}} with [[special:diff/762237|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:The Page Pilot]]), 21:46, 18 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762688|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:28, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762691|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:41, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762707|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:46, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762710|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:47, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762713|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:47, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762716|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:48, 21 September 2026 (UTC)
# [[:Wikipedia:AI noticeboard/2026-09-13 DVRTed]]: Added {{tl|AI-generated}} with [[special:diff/762720|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 14:50, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762772|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762774|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762776|this edit]] (relevant page: [[Wikipedia:AI noticeboard#Someone maybe]]), 17:04, 21 September 2026 (UTC)
# [[:User:DVRTed/sandbox2]]: Added {{tl|Prod llm}} with [[special:diff/762796|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 18:41, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|AI-generated}} with [[special:diff/762811|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:15, 21 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/762815|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:16, 21 September 2026 (UTC)
# [[:Wp/sas/Kabupatén Tangerang]]: Added {{tl|AI-generated}} with [[special:diff/762818|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 22:26, 21 September 2026 (UTC)
# [[:Testpage12]]: Added {{tl|Prod llm}} with [[special:diff/765089|this edit]] (relevant page: [[Wikipedia:AI noticeboard#User:DVRTed]]), 23:29, 29 September 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/765241|this edit]] (relevant page: [[Wikipedia:AI noticeboard/DVRTed]]), 09:20, 30 September 2026 (UTC)
# [[:BS Nymph]]: Added {{tl|Prod llm}} with [[special:diff/765391|this edit]] (relevant page: [[Wikipedia:AI noticeboard/2026-09-13 DVRTed]]), 22:41, 30 September 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|Prod llm}} with [[special:diff/782058|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 23:29, 31 October 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|Prod llm}} with [[special:diff/782062|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 23:31, 31 October 2026 (UTC)
# [[:Bob Morley]]: Added {{tl|Prod llm}} with [[special:diff/782418|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 21:59, 1 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783485|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:40, 8 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783494|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:51, 8 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|AI-generated}} with [[special:diff/783498|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 13:53, 8 November 2026 (UTC)
# [[:Bob Morley]]: Added {{tl|Prod llm}} with [[special:diff/783569|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 20:49, 8 November 2026 (UTC)
# [[:Test Page 817876]]: Added {{tl|Prod llm}} with [[special:diff/783581|this edit]] (relevant page: [[User:DVRTed/sandbox2]]), 20:53, 8 November 2026 (UTC)
7mdrihcnjv2c8yykhlklwox9pfb8ms9
User:Nardog/sandbox10.js
2
178684
783479
780891
2026-11-08T13:11:56Z
Nardog
40946
783479
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);
let msgPromise = new mw.Api().loadMessagesIfMissing([
'autocomment-top', 'colon-separator', 'parentheses', 'summary-preview'
]);
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.length > 1 && !this.newMiddle[0]) {
this.newTop.push(this.newMiddle.pop());
}
}
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 msgPromise;
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');
});
f4cptrj2nqbqookn9mpxvu9ygwlrwhu
783492
783479
2026-11-08T13:47:07Z
Nardog
40946
783492
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 {
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;
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());
}
}
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()).init();
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');
});
mmmn2stk8f7znpnvc5hgsdfqpmb5c55
783493
783492
2026-11-08T13:48:50Z
Nardog
40946
783493
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 {
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;
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());
}
}
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()).init();
console.log(asl);
}, 250);
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');
});
9m4fpm3qek23anqz6xvx60170pxcpae
783520
783493
2026-11-08T15:21:29Z
Nardog
40946
783520
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 {
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;
for (let i = 0; i < oldLen && i < newLen; i++) {
if (oldLines[i] !== newLines[i]) break;
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.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()).init();
console.log(asl);
}, 250);
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');
});
0ei055b2sppcbwavfcg64a7xaz1d454
783521
783520
2026-11-08T15:34:44Z
Nardog
40946
783521
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 {
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;
for (let i = 0; i < oldLen && i < newLen; i++) {
if (oldLines[i] !== newLines[i]) break;
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.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()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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()).init();
console.log(asl);
}, 250);
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');
});
24furojetb6r0hvlwls27ei32scz70s
783522
783521
2026-11-08T15:36:34Z
Nardog
40946
783522
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 {
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 < 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()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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()).init();
console.log(asl);
}, 250);
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');
});
ck442e6g6k37u6wyay2nh9k4cmhubq2
783523
783522
2026-11-08T15:37:01Z
Nardog
40946
783523
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 {
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;
for (let i = 0; i < oldLen && i < newLen; i++) {
if (oldLines[i] !== newLines[i]) break;
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.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()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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()).init();
console.log(asl);
}, 250);
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');
});
24furojetb6r0hvlwls27ei32scz70s
783528
783523
2026-11-08T16:21:45Z
Nardog
40946
783528
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 {
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 < 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()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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()).init();
console.log(asl);
}, 250);
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');
});
duatuwjbmtwrd7h0iq66guqndpncyp4
783533
783528
2026-11-08T16:50:26Z
Nardog
40946
783533
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle')) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$('#mw-content-text').empty().append(`<div id="asl">
<textarea id="asl-old"></textarea>
<form id="editform">
<textarea id="wpTextbox1"></textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`);
}
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 {
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
: isBp ? $('#asl-old').val() : $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 < 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()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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()).init();
console.log(asl);
}, 250);
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');
});
fu94gp73d0qykunty52szk5jsxrzhiw
783534
783533
2026-11-08T16:50:59Z
Nardog
40946
783534
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$('#mw-content-text').empty().append(`<div id="asl">
<textarea id="asl-old"></textarea>
<form id="editform">
<textarea id="wpTextbox1"></textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`);
}
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 {
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
: isBp ? $('#asl-old').val() : $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 < 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()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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()).init();
console.log(asl);
}, 250);
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');
});
20ru3mv601nvudylxeinrwj6catos3j
783540
783534
2026-11-08T17:00:37Z
Nardog
40946
783540
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$(`<div id="asl">
<textarea id="asl-old">${$('#asl-old').val()}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val()}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`).appendTo($('#mw-content-text').empty());
}
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 {
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
: isBp ? $('#asl-old').val() : $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 < 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()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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()).init();
console.log(asl);
}, 250);
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');
});
3bxiih66xzre9gd8z7rkysxxaoxio7a
783544
783540
2026-11-08T17:19:42Z
Nardog
40946
783544
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') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$(`<div id="asl">
<textarea id="asl-old">${$('#asl-old').val()}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val()}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`).appendTo($('#mw-content-text').empty());
}
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 {
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
: isBp ? $('#asl-old').val() : $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 < 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, ...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()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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()).init();
console.log(asl);
}, 250);
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');
});
qc0j76nwz2dmdjhnp9lrx3weizll7dd
783546
783544
2026-11-08T17:37:59Z
Nardog
40946
783546
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('#asl ');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div id="asl">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
</div>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>`).appendTo($('#mw-content-text').empty());
}
let form = document.getElementById('editform');
if (!form) return;
let formData = new FormData(form);
if (formData.get('wpSection') === '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.section = $('input[name="wpSection"]').val();
this.headings = [];
this.templates = new Set();
this.ids = [];
}
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
: isBp ? $('#asl-old').val() : $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 < 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, ...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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: this.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 this.section > 0;
}
});
}
setTopSectionLink() {
if (!this.headings.length && !this.topmostLevel && (
this.section === '0' || !this.section && this.newBottom.some(line => Asl.re.test(line))
)) {
this.sectionLink = '';
}
}
getTemplateCodes() {
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() {
if (this.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 === this.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()).init();
console.log(asl);
}, 250);
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');
});
8jm1ru09y9pncwd3o6ffoypxde8a3eq
783548
783546
2026-11-08T17:44:27Z
Nardog
40946
783548
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('#asl ');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div id="asl">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`).appendTo($('#mw-content-text').empty());
}
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() {
section = $('input[name="wpSection"]').val(); //
this.headings = [];
this.templates = new Set();
this.ids = [];
}
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
: isBp ? $('#aslold').val() : $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 < 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, ...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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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.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() {
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()).init();
console.log(asl);
}, 250);
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');
});
85tcmd4ye260m0e9ogbgiaa47n7rhd5
783549
783548
2026-11-08T17:46:16Z
Nardog
40946
783549
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('#asl ');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div id="asl">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`).appendTo($('#mw-content-text').empty());
}
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() {
section = $('input[name="wpSection"]').val(); //
this.headings = [];
this.templates = new Set();
this.ids = [];
}
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 = (!isBp && isOld || mw.config.get('wgAction') === 'submit')
? (await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}`
)).source
: isBp ? $('#aslold').val() : $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 < 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, ...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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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.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() {
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()).init();
console.log(asl);
}, 250);
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');
});
1lfafagmfr1dz5jzmjz5j4lpkvj7uwc
783550
783549
2026-11-08T17:46:27Z
Nardog
40946
783550
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('#asl ');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div id="asl">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`).appendTo($('#mw-content-text').empty());
}
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() {
section = $('input[name="wpSection"]').val(); //
this.headings = [];
this.templates = new Set();
this.ids = [];
}
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 = !isBp && (isOld || mw.config.get('wgAction') === 'submit')
? (await new mw.Rest().get(
`/v1/page/${encodeURIComponent(mw.config.get('wgPageName'))}`
)).source
: isBp ? $('#aslold').val() : $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 < 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, ...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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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.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() {
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()).init();
console.log(asl);
}, 250);
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');
});
fls0sl1mbqfs7l8z0k3z8qy6vi8ckfd
783552
783550
2026-11-08T17:48:01Z
Nardog
40946
783552
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('#asl ');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div id="asl">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`).appendTo($('#mw-content-text').empty());
}
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() {
section = $('input[name="wpSection"]').val(); //
this.headings = [];
this.templates = new Set();
this.ids = [];
}
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 = isBp
? $('#aslold').val()
: 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 < 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, ...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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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.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() {
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()).init();
console.log(asl);
}, 250);
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');
});
je108y0b4achqth9fgqsx4n7v26wgys
783553
783552
2026-11-08T17:48:40Z
Nardog
40946
783553
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('#asl ');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div id="asl">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`).appendTo($('#mw-content-text').empty());
}
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() {
if (isBp) {
section = $('input[name="wpSection"]').val();
}
this.headings = [];
this.templates = new Set();
this.ids = [];
}
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 = isBp
? $('#aslold').val()
: 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 < 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, ...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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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.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() {
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()).init();
console.log(asl);
}, 250);
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');
});
dfgtubol8f22glylfmfa739uc497qt2
783554
783553
2026-11-08T17:52:32Z
Nardog
40946
783554
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('#asl ');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div id="asl">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`).appendTo($('#mw-content-text').empty());
}
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() {
if (isBp) {
section = $('input[name="wpSection"]').val();
}
this.headings = [];
this.templates = new Set();
this.ids = [];
}
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 = isBp
? $('#aslold').val()
: 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 < 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, ...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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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.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() {
if (!isBp && 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()).init();
console.log(asl);
}, 250);
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');
});
rk8h2i6lilb482bsij66kw4e2ly70ub
783555
783554
2026-11-08T18:00:26Z
Nardog
40946
783555
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('#asl ');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`#asl {
display: flex;
}
#asl > * {
flex-grow: 1;
flex-basis: 0;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div id="asl">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`).appendTo($('#mw-content-text').empty());
}
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() {
if (isBp) {
section = $('input[name="wpSection"]').val();
}
this.headings = [];
this.templates = new Set();
this.ids = [];
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 = isBp
? $('#aslold').val()
: 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 < 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() {
let lines = section > 0
? [...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)]
: [...this.oldMiddle, ...this.newMiddle.slice(1)];
lines.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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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.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() {
if (!isBp && 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);
}, 250);
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');
});
5306fo9q5nscbxlfyd9u0xl2ebykz87
783556
783555
2026-11-08T18:08:18Z
Nardog
40946
783556
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('.autosectionlink');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`.autosectionlink-test {
display: flex;
}
.autosectionlink-test > * {
flex-grow: 1;
flex-basis: 0;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div class="autosectionlink-test">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>`).appendTo($('#mw-content-text').empty());
}
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() {
if (isBp) {
section = $('input[name="wpSection"]').val();
}
this.headings = [];
this.templates = new Set();
this.ids = [];
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 = isBp
? $('#aslold').val()
: 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 < 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() {
let lines = section > 0
? [...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)]
: [...this.oldMiddle, ...this.newMiddle.slice(1)];
lines.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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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.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() {
if (!isBp && 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);
}, 250);
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');
});
956ez99r3oseagwujn8veup33rdsf2o
783557
783556
2026-11-08T18:14:40Z
Nardog
40946
783557
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('.autosectionlink');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`.autosectionlink-test {
display: flex;
}
.autosectionlink-test > * {
flex-grow: 1;
flex-basis: 0;
}
#asldump {
tab-size: 4;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div class="autosectionlink-test">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>
<pre id="asldump"></pre>`).appendTo($('#mw-content-text').empty());
}
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() {
if (isBp) {
section = $('input[name="wpSection"]').val();
}
this.headings = [];
this.templates = new Set();
this.ids = [];
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();
if (isBp) {
$('#asldump').text(JSON.stringify(this, null, '\t'));
}
}
async dissect() {
let source = isBp
? $('#aslold').val()
: 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 < 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() {
let lines = section > 0
? [...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)]
: [...this.oldMiddle, ...this.newMiddle.slice(1)];
lines.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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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.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() {
if (!isBp && 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);
}, 250);
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');
});
hzo8snh7iglisdlsuwz5hqvx07h9fme
783562
783557
2026-11-08T19:07:51Z
Nardog
40946
783562
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('.autosectionlink');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`.autosectionlink-test {
display: flex;
}
.autosectionlink-test > * {
flex-grow: 1;
flex-basis: 0;
}
#asldump {
tab-size: 4;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div class="autosectionlink-test">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
</div>
<pre id="asldump"></pre>`).appendTo($('#mw-content-text').empty());
}
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() {
if (isBp) {
section = $('input[name="wpSection"]').val();
}
this.headings = [];
this.templates = new Set();
this.ids = [];
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();
if (isBp) {
$('#asldump').text(JSON.stringify(this, null, '\t'));
}
}
async dissect() {
let source = isBp
? $('#aslold').val()
: 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++;
}
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());
}
}
getTopmostLevel() {
let lines = section > 0
? [...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)]
: [...this.oldMiddle, ...this.newMiddle.slice(1)];
lines.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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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.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() {
if (!isBp && 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);
}, 250);
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');
});
sxresm7wks4oipmd9z4tz6lhamcrigj
783563
783562
2026-11-08T19:17:09Z
Nardog
40946
783563
javascript
text/javascript
$('#wpTextbox1').off('input', window.aslEh);
mw.hook('ext.CodeMirror.input').remove(window.aslEh);
$('.autosectionlink-toggle').remove();
$('head > style').filter(function () {
return this.textContent.includes('.autosectionlink');
}).remove();
((['edit', 'submit'].includes(mw.config.get('wgAction')) &&
mw.config.get('wgArticleId') &&
mw.config.get('wgPageContentModel') === 'wikitext') ||
mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' &&
/^[^/]+\/asl$/.test(mw.config.get('wgTitle'))) &&
$(async () => {
let isBp = mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage';
if (isBp) {
mw.loader.addStyleTag(`.autosectionlink-test {
display: flex;
height: calc(85vh + 14px);
}
.autosectionlink-test > * {
flex-grow: 1;
flex-basis: 0;
}
#asldump {
tab-size: 4;
margin: 0;
}`);
$(`<input id="wpSection" name="wpSection" value="${$('#wpSection').val() || ''}" placeholder="section">
<div class="autosectionlink-test">
<textarea id="aslold">${$('#aslold').val() || ''}</textarea>
<form id="editform">
<textarea id="wpTextbox1">${$('#wpTextbox1').val() || ''}</textarea>
<div id='wpSummaryWidget' class='oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text oo-ui-textInputWidget-php' data-ooui='{"_":"OO.ui.TextInputWidget","maxLength":500,"name":"wpSummary","inputId":"wpSummary","tabIndex":1,"title":"Enter a short summary","accessKey":"b","required":false}'><input type='text' tabindex='1' title='Enter a short summary [b]' accesskey='b' name='wpSummary' value='' id='wpSummary' maxlength='500' spellcheck='true' class='oo-ui-inputWidget-input' /><span class='oo-ui-iconElement-icon oo-ui-iconElement-noIcon'></span><span class='oo-ui-indicatorElement-indicator oo-ui-indicatorElement-noIndicator'></span></div>
</form>
<pre id="asldump"></pre>
</div>`).appendTo($('#mw-content-text').empty());
}
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() {
if (isBp) {
section = $('input[name="wpSection"]').val();
}
this.headings = [];
this.templates = new Set();
this.ids = [];
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();
if (isBp) {
$('#asldump').text(JSON.stringify(this, null, '\t'));
}
}
async dissect() {
let source = isBp
? $('#aslold').val()
: 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++;
}
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());
}
}
getTopmostLevel() {
let lines = section > 0
? [...this.oldMiddle.slice(1), ...this.newMiddle.slice(1)]
: [...this.oldMiddle, ...this.newMiddle.slice(1)];
lines.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() {
let lines = this.newMiddle.length
? [...this.newTop, this.newMiddle[0]].reverse()
: this.oldMiddle.length ? this.newTop.toReversed()
: 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.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() {
if (!isBp && 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);
}, 250);
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');
});
nn7z5vapwfrf0550fpbqq3tbkcpetjr
User:Nardog/sandbox11.js
2
178772
783502
782616
2026-11-08T14:06:31Z
Nardog
40946
783502
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.links.length) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
this.line.links.forEach((link, j) => {
if (this.noRedirLinks.has(link)) {
this.noRedirLinks.add($links[j]);
}
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side, links: [] };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.links.length) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, name, mid, data) {
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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.line.links.push(...links);
}
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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
g343q8rmbw8za1yme9yqnfk594vc0mz
783503
783502
2026-11-08T14:07:45Z
Nardog
40946
783503
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.links.length) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
this.line.links.forEach((link, j) => {
if (this.noRedirLinks.has(link)) {
this.noRedirLinks.add($links[j]);
}
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side, links: [] };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.links.length) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, name, mid, data) {
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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.line.links.push(...links);
}
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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
nm4hu5y11p558fw3ei09dzsluev1ktp
783507
783503
2026-11-08T14:20:46Z
Nardog
40946
783507
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.links.length) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
this.line.links.forEach((link, j) => {
if (this.noRedirLinks.has(link)) {
this.noRedirLinks.add($links[j]);
}
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side, links: [] };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.links.length) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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.line.links.push(...links);
}
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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
iztxykej4j1v9yskhgdz8vi54s1228n
783508
783507
2026-11-08T14:25:50Z
Nardog
40946
783508
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.links.length) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
this.line.links.forEach((link, j) => {
if (this.noRedirLinks.has(link)) {
debugger;
this.noRedirLinks.add($links[j]);
}
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side, links: [] };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.links.length) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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.line.links.push(...links);
}
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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
7i2jfn723ltc3a95zh422pgtr2hpxrn
783509
783508
2026-11-08T14:27:45Z
Nardog
40946
783509
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.links.length) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
this.line.links.forEach((link, j) => {
if (link.title === 'MOS:NOTOOLTIPS') debugger;
if (this.noRedirLinks.has(link)) {
this.noRedirLinks.add($links[j]);
}
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side, links: [] };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.links.length) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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.line.links.push(...links);
}
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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
let noRedirLinks = this.noRedirLinks;
$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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
ag6li864813crvzevo5b04msvj8p7jf
783510
783509
2026-11-08T14:31:44Z
Nardog
40946
783510
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.data('smartdiff', this);
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 = $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.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.links.length) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link').each(function () {
$(this).data('smartdiff')?.obj.links.push(this);
});
this.line.links.forEach((link, j) => {
if (this.noRedirLinks.has(link)) {
this.noRedirLinks.add($links[j]);
}
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side, links: [] };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.links.length) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.links.push(...links);
}
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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
9h8c12h21fwxx2q7dowu5vdoex2m7db
783511
783510
2026-11-08T14:32:02Z
Nardog
40946
783511
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.links.length) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link').each(function () {
$(this).data('smartdiff')?.obj.links.push(this);
});
this.line.links.forEach((link, j) => {
if (this.noRedirLinks.has(link)) {
this.noRedirLinks.add($links[j]);
}
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side, links: [] };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.links.length) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.links.push(...links);
}
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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
mqflsaggohduu9rnv3aje7bk4r7d14y
783512
783511
2026-11-08T14:32:59Z
Nardog
40946
783512
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.links.length) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
$links.eq(j).data('smartdiff')?.obj.links.push(this);
});
this.line.links.forEach((link, j) => {
if (this.noRedirLinks.has(link)) {
this.noRedirLinks.add($links[j]);
}
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side, links: [] };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.links.length) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.links.push(...links);
}
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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
658qx7l95spfae2c79b2buowzvmhfkj
783513
783512
2026-11-08T14:52:55Z
Nardog
40946
783513
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
div.replaceChildren(...this.cloneChildren(this.line.div));
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
cloneChildren(node) {
return [...node.childNodes].map(child => {
let clone = child.cloneNode();
clone.append(...this.cloneChildren(child));
if (child.tagName === 'A') {
$(child).data('smartdiff')?.obj.links.push(this);
if (this.noRedirLinks.has(child)) {
this.noRedirLinks.add(clone);
}
}
return clone;
});
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
fetgyv8pzf4qplb23cx1gfnu5sy4yzw
783514
783513
2026-11-08T14:53:52Z
Nardog
40946
783514
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
div.replaceChildren(...this.cloneChildren(this.line.div));
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
cloneChildren(node) {
return [...node.childNodes].map(child => {
let clone = child.cloneNode();
if (child.nodeType === 1) {
clone.append(...this.cloneChildren(child));
if (child.tagName === 'A') {
$(child).data('smartdiff')?.obj.links.push(this);
if (this.noRedirLinks.has(child)) {
this.noRedirLinks.add(clone);
}
}
}
return clone;
});
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
lgc48am1th6uy5bpq3rsy2klos5c3ir
783515
783514
2026-11-08T14:54:12Z
Nardog
40946
783515
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
div.replaceChildren(...this.cloneChildren(this.line.div));
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
cloneChildren(node) {
return [...node.childNodes].map(child => {
let clone = child.cloneNode();
if (child.nodeType === 1) {
clone.append(...this.cloneChildren(child));
if (child.tagName === 'A') {
$(child).data('smartdiff')?.obj.links.push(clone);
if (this.noRedirLinks.has(child)) {
this.noRedirLinks.add(clone);
}
}
}
return clone;
});
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
cna3w9jeuhed5us8mmchelh23ij0gfk
783516
783515
2026-11-08T14:55:00Z
Nardog
40946
783516
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.links.length) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
$links.eq(j).data('smartdiff')?.obj.links.push(this);
});
this.line.links.forEach((link, j) => {
if (this.noRedirLinks.has(link)) {
this.noRedirLinks.add($links[j]);
}
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side, links: [] };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.links.length) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.links.push(...links);
}
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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
658qx7l95spfae2c79b2buowzvmhfkj
783517
783516
2026-11-08T14:56:34Z
Nardog
40946
783517
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
let data = $links.eq(j).data('smartdiff');
data?.obj.links.push(this);
if (data?.noRedirect) {
this.noRedirLinks.add(this);
}
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
cgg71plx0hxgzylo7e2dx8xpwc1e6au
783518
783517
2026-11-08T14:57:24Z
Nardog
40946
783518
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.data('smartdiff', this);
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 = $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();
this.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each((j, link) => {
let data = $links.eq(j).data('smartdiff');
data?.obj.links.push(link);
if (data?.noRedirect) {
this.noRedirLinks.add(link);
}
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
eac9ase4htqo02q567zd2ik7qa9wbt8
783519
783518
2026-11-08T14:58:42Z
Nardog
40946
783519
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.data('smartdiff', this);
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 = $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.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
$links.eq(j).data('smartdiff')?.obj.links.push(this);
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
di4hnzd7cpnsgr1rpf6g9rh3i6d8llp
783524
783519
2026-11-08T15:55:17Z
Nardog
40946
783524
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.data('smartdiff', this);
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 = $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.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
$links.eq(j).data('smartdiff')?.obj.links.push(this);
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
return link;
}
decodeWikitext(input, decodePercents) {
let s = input;
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
ik3wi5rfax1y0kbiohhbsg1pegzrtil
783527
783524
2026-11-08T16:13:20Z
Nardog
40946
783527
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.data('smartdiff', this);
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 = $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.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
$links.eq(j).data('smartdiff')?.obj.links.push(this);
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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 (midTags[i]?.closingTag && tokens[newStart - 1]?.openingTag &&
midTags[i].closingTag.tagName === tokens[newStart - 1].openingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
i += 2;
}
let newEnd = end, j = midTags.length - 1;
while (midTags[j]?.openingTag && tokens[newEnd]?.closingTag &&
midTags[j].openingTag.tagName === tokens[newEnd].closingTag.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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
return link;
}
decodeWikitext(input, decodePercents) {
let s = input;
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed = unprocessed.slice(50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
e5e635hlj47i83w3brindk6sy9f7e51
783532
783527
2026-11-08T16:30:03Z
Nardog
40946
783532
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.data('smartdiff', this);
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 = $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.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
$links.eq(j).data('smartdiff')?.obj.links.push(this);
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /((?<!{){{(?!{)|(?<!})}}(?!}))|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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 (midTags[i]?.closingTag && tokens[newStart - 1]?.openingTag &&
midTags[i].closingTag.tagName === tokens[newStart - 1].openingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
i += 2;
}
let newEnd = end, j = midTags.length - 1;
while (midTags[j]?.openingTag && tokens[newEnd]?.closingTag &&
midTags[j].openingTag.tagName === tokens[newEnd].closingTag.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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
return link;
}
decodeWikitext(input, decodePercents) {
let s = input;
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed.splice(0, 50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
d1wlys4w624nls7gz04hd7arquzd09f
783648
783532
2026-11-09T03:41:43Z
Nardog
40946
783648
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.data('smartdiff', this);
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 = $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.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
$links.eq(j).data('smartdiff')?.obj.links.push(this);
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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 (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 (!s.startsWith('#')) {
let isMw;
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
isMw = match && SmartDiff.magicWords.includes(match[0]);
}
if (!isMw) {
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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /({{{?|}}}?)|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else if (brackets.s === '}}') {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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 (midTags[i]?.closingTag && tokens[newStart - 1]?.openingTag &&
midTags[i].closingTag.tagName === tokens[newStart - 1].openingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
i += 2;
}
let newEnd = end, j = midTags.length - 1;
while (midTags[j]?.openingTag && tokens[newEnd]?.closingTag &&
midTags[j].openingTag.tagName === tokens[newEnd].closingTag.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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
return link;
}
decodeWikitext(input, decodePercents) {
let s = input;
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed.splice(0, 50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
f88fz9jgjgepw6erbzmzdtg9gy3tf2g
783649
783648
2026-11-09T03:43:35Z
Nardog
40946
783649
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.data('smartdiff', this);
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 = $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.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
$links.eq(j).data('smartdiff')?.obj.links.push(this);
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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.startsWith('{{{')) {
if (pre.s.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} 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 (!s.startsWith('#')) {
let isMw;
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
isMw = match && SmartDiff.magicWords.includes(match[0]);
}
if (!isMw) {
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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /({{{?|}}}?)|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else if (brackets.s === '}}') {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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 (midTags[i]?.closingTag && tokens[newStart - 1]?.openingTag &&
midTags[i].closingTag.tagName === tokens[newStart - 1].openingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
i += 2;
}
let newEnd = end, j = midTags.length - 1;
while (midTags[j]?.openingTag && tokens[newEnd]?.closingTag &&
midTags[j].openingTag.tagName === tokens[newEnd].closingTag.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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
return link;
}
decodeWikitext(input, decodePercents) {
let s = input;
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed.splice(0, 50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
8ace30xx87c7knfblt1z6hghs5nqx2l
783650
783649
2026-11-09T03:45:36Z
Nardog
40946
783650
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.data('smartdiff', this);
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 = $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.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
$links.eq(j).data('smartdiff')?.obj.links.push(this);
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[0];
}
verify(div, newDiv) {
let origHtml = div.innerHTML, $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), 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), t;
if (!pre.s.startsWith('{{{')) {
if (pre.s.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} 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 (!s.startsWith('#')) {
let isMw;
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
isMw = match && SmartDiff.magicWords.includes(match[0]);
}
if (!isMw) {
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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /({{{?|}}}?)|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else if (brackets.s === '}}') {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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 (midTags[i]?.closingTag && tokens[newStart - 1]?.openingTag &&
midTags[i].closingTag.tagName === tokens[newStart - 1].openingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
i += 2;
}
let newEnd = end, j = midTags.length - 1;
while (midTags[j]?.openingTag && tokens[newEnd]?.closingTag &&
midTags[j].openingTag.tagName === tokens[newEnd].closingTag.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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
return link;
}
decodeWikitext(input, decodePercents) {
let s = input;
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed.splice(0, 50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
7e5eorigpmypsjv1m4hn17xomnlgqzo
783651
783650
2026-11-09T03:46:02Z
Nardog
40946
783651
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.data('smartdiff', this);
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 = $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.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
$links.eq(j).data('smartdiff')?.obj.links.push(this);
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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), 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), t;
if (!pre.s.startsWith('{{{')) {
if (pre.s.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} 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 (!s.startsWith('#')) {
let isMw;
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
isMw = match && SmartDiff.magicWords.includes(match[0]);
}
if (!isMw) {
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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /({{{?|}}}?)|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else if (brackets.s === '}}') {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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 (midTags[i]?.closingTag && tokens[newStart - 1]?.openingTag &&
midTags[i].closingTag.tagName === tokens[newStart - 1].openingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
i += 2;
}
let newEnd = end, j = midTags.length - 1;
while (midTags[j]?.openingTag && tokens[newEnd]?.closingTag &&
midTags[j].openingTag.tagName === tokens[newEnd].closingTag.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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
return link;
}
decodeWikitext(input, decodePercents) {
let s = input;
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed.splice(0, 50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
erb96sfuou43teug6jc92m469e9zalh
783652
783651
2026-11-09T03:46:22Z
Nardog
40946
783652
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.data('smartdiff', this);
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 = $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.init();
}
async init($rows = this.$diff) {
for (let div of $rows.find(
'.diff-deletedline > div, .mw-diff-inline-deleted'
)) {
await this.processLine(div, 'old');
}
for (let div of $rows.find(
'.diff-addedline > div, .mw-diff-inline-added, .mw-diff-inline-context'
)) {
await this.processLine(div, 'new');
}
for (let div of $rows.find(
'.mw-diff-inline-changed, .mw-diff-inline-moved'
)) {
await this.processLine(div, 'both');
}
for (let [i, div] of [...$rows.find('.diff-context > div')].entries()) {
if (i % 2) {
if (this.line.hasLink) {
if (this.line.usesSideProp) {
await this.processLine(div, 'new');
} else {
let $clone = $(this.line.div).clone(true);
let $links = $clone.find('.smartdiff-link');
$links.each(function (j) {
$links.eq(j).data('smartdiff')?.obj.links.push(this);
});
div.replaceChildren(...$clone.contents());
}
}
} else {
await this.processLine(div, 'old');
}
}
this.queryTitles();
if (this.hasError) {
mw.notify('SmartDiff error', { type: 'warn' });
}
console.log(performance.now() - this.now);
}
async processLine(div, side) {
this.line = { div, side };
if (div.querySelector('a[href]:not(.mw-diff-movedpara-left, .mw-diff-movedpara-right)')) return;
let tokens;
if (side === 'both') {
tokens = this.tokenize(
div,
'del, .mw-diff-movedpara-left, .mw-diff-movedpara-right'
);
this.line.side = 'new';
await this.exec(tokens);
this.escapeTags(tokens, 'ins');
this.unescapeTags(tokens, 'del');
this.line.side = 'old';
await this.exec(tokens, true);
this.unescapeTags(tokens);
} else {
tokens = this.tokenize(div);
await this.exec(tokens);
}
if (!this.line.hasLink) 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) {
for (let i = tokens.length - 1; i >= 0; i--) {
let token = tokens[i];
if (token.esc && (!selector || token.tag.matches(selector))) {
tokens.splice(i, 1, ...token.esc);
}
}
}
unescape(tokens) {
let openTags = [$('<div>')[0]], lastText = '';
tokens.forEach(token => {
if (token.openingTag) {
if (lastText) {
openTags.at(-1).append(lastText);
lastText = '';
}
let el = token.openingTag;
if (!token.isLink) {
el = el.cloneNode();
}
openTags.at(-1).append(el);
openTags.push(el);
} else if (token.closingTag) {
let el = openTags.pop();
if (!openTags.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${openTags[0].innerHTML}`;
}
if (lastText) {
el.append(lastText);
lastText = '';
}
} else {
lastText += token.s;
}
});
if (lastText) {
openTags.at(-1).append(lastText);
}
if (openTags.length !== 1) {
throw `too many opening tags.\n\n${openTags[0].innerHTML}`;
}
return openTags[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), 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), t;
if (!pre.s.startsWith('{{{')) {
if (pre.s.includes('invoke')) {
t = mw.Title.makeTitle(828, s);
} 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 (!s.startsWith('#')) {
let isMw;
if (!pre.s.includes('msg') && !pre.s.includes('raw')) {
let match = s.match(/^[^:]+(?::(?=.)|$)/);
isMw = match && SmartDiff.magicWords.includes(match[0]);
}
if (!isMw) {
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) {
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, level: 0 }
);
}
}
}
tokens.splice(postStart, postEnd - postStart, ...newPost);
}
static templateArgsRe = /({{{?|}}}?)|\|\s*(?:([1-9]\d*)\s*=)?\s*(?:<tvar(?: [^>]*)?>\s*)?(.*?)(?:\s*<\/tvar>)?\s*(?=\||{{!}}|}}|$)/dg;
templateArgsCallback(tokens, brackets, name, mid, data) {
if (data.level < 0) return;
if (brackets) {
if (brackets.s === '{{') {
data.level++;
} else if (brackets.s === '}}') {
data.level--;
}
return;
}
if (data.level) return;
let n = name ? name.s : data.i++;
if (n < data.start || n > data.end ||
(n % 2 ? data.skipOdd : data.skipEven) ||
!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(token => token.isLink)) {
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.isLink && !token.esc) return;
if (token.s && !token.isLink) {
if (!indices.length || last?.isLink || 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, data;
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;
data = { obj, noRedirect };
} 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 (data) {
$(links).data('smartdiff', data);
}
}
this.line.hasLink = 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 (midTags[i]?.closingTag && tokens[newStart - 1]?.openingTag &&
midTags[i].closingTag.tagName === tokens[newStart - 1].openingTag.tagName
) {
midTags.unshift(tokens[newStart - 1]);
newStart--;
i += 2;
}
let newEnd = end, j = midTags.length - 1;
while (midTags[j]?.openingTag && tokens[newEnd]?.closingTag &&
midTags[j].openingTag.tagName === tokens[newEnd].closingTag.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, isLink: true }];
let post = [{ closingTag: link, isLink: true }];
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.isLink = true;
}
});
return link;
}
decodeWikitext(input, decodePercents) {
let s = input;
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.line.side]) {
this.line.usesSideProp = !this.isSamePage;
return this[this.line.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];
let $links = $(obj.links);
if (obj.classes) {
$links.addClass(obj.classes);
}
if (obj.redirect) {
$links.attr('href', function () {
return mw.util.getUrl(
obj.canonical || title,
$(this).data('smartdiff')?.noRedirect && { 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) {
$links.attr('href', function () {
return url + this.hash;
});
}
}
if (obj.canonical) {
$links.attr('title', obj.canonical);
}
$links.removeData('smartdiff');
obj.links = [];
});
unprocessed.splice(0, 50);
}
}
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 = $diff.data('smartdiff');
if (sd) {
sd.init(
$rows.filter(function () {
return this.checkVisibility();
})
);
} else {
window.sd = new SmartDiff($diff);
}
})));
});
5i577f9mrn0otrau5pu5bks9k0x7l0q
Ibero-American MAB Network (IberoMAB)
0
179378
783535
783311
2026-11-08T16:51:14Z
OrangeLemonadee
76341
Added environmental emphasis to lead section
783535
wikitext
text/x-wiki
The '''Ibero-American MAB Network''' ('''IberoMAB''') is a regional network established in 1992 within the framework of the Man and the Biosphere (MAB) Programme of the United Nations Educational, Scientific and Cultural Organization (UNESCO). It connects countries in Latin America and the Caribbean, as well as Spain and Portugal, to promote cooperation among biosphere reserves and support sustainable development. By bridging diverse ecosystems ranging from tropical rainforests to marine archipelagos, IberoMAB serves as a vital platform for addressing shared environmental vulnerabilities and climate change impacts across member states. The network's objectives include strengthening national MAB committees, fostering cooperation among member countries and encouraging the establishment of new biosphere reserves.<ref name="UNESCO-IberoMAB">{{cite web |title=Regional and Thematic Networks |url=https://www.unesco.org/en/mab/networks |website=UNESCO |access-date=21 October 2026}}</ref>
== History ==
The Ibero-American MAB Network was created in 1992 to strengthen cooperation among countries participating in UNESCO's Man and the Biosphere Programme. Its establishment provided a regional framework for sharing experiences and supporting biosphere reserves in Latin America, the Caribbean, Spain and Portugal.<ref name="OAPN-IberoMAB" />
In May 2018, the network's Action Plan for 2018–2025 was adopted in Santa Marta, Colombia. The plan established a framework for the network's work in the following years.<ref name="OAPN-IberoMAB">{{cite web |title=¿Qué es la Red IberoMaB? |url=https://www.miteco.gob.es/es/parques-nacionales-oapn/reservas-biosfera-mab/redes-regionales-y-tematicas/iberomab.html |website=Ministerio para la Transición Ecológica y el Reto Demográfico |access-date=5 November 2026}}</ref>
In October 2020, IberoMAB published a declaration addressing the COVID-19 pandemic and its effects on communities and the environment. The declaration emphasized sustainable recovery, community participation, scientific research and the role of biosphere reserves in protecting biodiversity and supporting human well-being.<ref name="Declaration">{{cite web |title=IberoMAB Biosphere Reserves pledge for a sustainable 'new normal' to overcome the COVID19 crisis |url=https://www.unesco.org/en/articles/iberomab-biosphere-reserves-pledge-sustainable-new-normal-overcome-covid19-crisis |website=UNESCO |date=15 October 2020 |access-date=21 October 2026}}</ref>
In 2023, the network held its second congress in the Trifinio-Fraternidad Transboundary Biosphere Reserve. Participants reviewed progress on regional cooperation and assessed the implementation of the Lima Action Plan 2016–2025 and the IberoMAB Action Plan 2018–2025.<ref name="UNESCO-IberoMAB" />
== Geographic scope and membership ==
''This section has not yet been completed.''
== Objectives and activities ==
IberoMAB aims to strengthen the implementation of UNESCO's Man and the Biosphere Programme across its member countries. Its main objectives include:
* Strengthening national committees responsible for the MAB Programme.
* Improving cooperation and communication between member countries and biosphere reserves.
* Supporting the establishment and development of biosphere reserves.
* Promoting biodiversity conservation and sustainable development.
* Encouraging the exchange of scientific knowledge, experiences and good practices.<ref name="OAPN-IberoMAB" />
The network also supports initiatives addressing environmental and social challenges. In its 2020 declaration on the COVID-19 pandemic, IberoMAB encouraged community participation, the involvement of young people and vulnerable groups, scientific and technical research, and the integration of biodiversity and sustainable development into public policies.<ref name="Declaration" />
A UNESCO project running from 2026 to 2027 aims to strengthen cooperation and the exchange of good practices in biodiversity conservation and management, community participation, sustainable development and green economies among biosphere reserves in Ibero-America and thematic networks.<ref name="Project">{{cite web |title=MAB Secretariat support to strategic initiatives for sustainable development |url=https://core.unesco.org/en/project/3210211102 |website=UNESCO Core Data Portal |access-date=5 November 2026}}</ref>
== Organization and cooperation ==
IberoMAB operates within UNESCO's Man and the Biosphere Programme and brings together national MAB committees and representatives from participating countries. Its activities provide opportunities for members to exchange knowledge, discuss common challenges and coordinate regional priorities.
The network has a Bureau that supports its coordination. According to UNESCO, Honduras serves as president, Peru as vice-president and Spain holds the secretariat.<ref name="UNESCO-IberoMAB" /> IberoMAB also participates in initiatives that strengthen cooperation between biosphere reserves and thematic networks. These initiatives encourage the exchange of expertise and good practices in biodiversity conservation, sustainable development and environmental management.<ref name="Project" />
== See also ==
* [[Man and the Biosphere Programme]]
* [[World Network of Biosphere Reserves]]
* [[Biosphere reserve]]
* [[UNESCO]]
== References ==
{{reflist}}
bp4q5j6pha03xw6b7yn5hpxwrw0t46g
783537
783535
2026-11-08T16:55:48Z
OrangeLemonadee
76341
783537
wikitext
text/x-wiki
The '''Ibero-American MAB Network''' ('''IberoMAB''') is a regional network established in 1992 within the framework of the Man and the Biosphere (MAB) Programme of the United Nations Educational, Scientific and Cultural Organization (UNESCO). It connects countries in Latin America and the Caribbean, as well as Spain and Portugal, to promote cooperation among biosphere reserves and support sustainable development. By bridging diverse ecosystems ranging from tropical rainforests to marine archipelagos, IberoMAB serves as a vital platform for addressing shared environmental vulnerabilities and climate change impacts across member states. The network's objectives include strengthening national MAB committees, fostering cooperation among member countries and encouraging the establishment of new biosphere reserves.<ref name="UNESCO-IberoMAB">{{cite web |title=Regional and Thematic Networks |url=https://www.unesco.org/en/mab/networks |website=UNESCO |access-date=21 October 2026}}</ref>
== History ==
The Ibero-American MAB Network was created in 1992 to strengthen cooperation among countries participating in UNESCO's Man and the Biosphere Programme. Its establishment provided a regional framework for sharing experiences and supporting biosphere reserves in Latin America, the Caribbean, Spain and Portugal.<ref name="OAPN-IberoMAB" />
In May 2018, the network's Action Plan for 2018–2025 was adopted in Santa Marta, Colombia. The plan established a framework for the network's work in the following years.<ref name="OAPN-IberoMAB">{{cite web |title=¿Qué es la Red IberoMaB? |url=https://www.miteco.gob.es/es/parques-nacionales-oapn/reservas-biosfera-mab/redes-regionales-y-tematicas/iberomab.html |website=Ministerio para la Transición Ecológica y el Reto Demográfico |access-date=5 November 2026}}</ref>
In October 2020, IberoMAB published a declaration addressing the COVID-19 pandemic and its effects on communities and the environment. The declaration emphasized sustainable recovery, community participation, scientific research and the role of biosphere reserves in protecting biodiversity and supporting human well-being.<ref name="Declaration">{{cite web |title=IberoMAB Biosphere Reserves pledge for a sustainable 'new normal' to overcome the COVID19 crisis |url=https://www.unesco.org/en/articles/iberomab-biosphere-reserves-pledge-sustainable-new-normal-overcome-covid19-crisis |website=UNESCO |date=15 October 2020 |access-date=21 October 2026}}</ref>
In 2023, the network held its second congress in the Trifinio-Fraternidad Transboundary Biosphere Reserve. Participants reviewed progress on regional cooperation and assessed the implementation of the Lima Action Plan 2016–2025 and the IberoMAB Action Plan 2018–2025.<ref name="UNESCO-IberoMAB" />
== Geographic scope and membership ==
''This section has not yet been completed.''
== Objectives and activities ==
IberoMAB aims to strengthen the implementation of UNESCO's Man and the Biosphere Programme across its member countries. Its main objectives include:
* Strengthening national committees responsible for the MAB Programme.
* Improving cooperation and communication between member countries and biosphere reserves.
* Supporting the establishment and development of biosphere reserves.
* Promoting biodiversity conservation and sustainable development.
*Empowering local communities and young researchers to take an active role in environmental monitoring and ecological stewardship.
* Encouraging the exchange of scientific knowledge, experiences and good practices.<ref name="OAPN-IberoMAB" />
The network also supports initiatives addressing environmental and social challenges. In its 2020 declaration on the COVID-19 pandemic, IberoMAB encouraged community participation, the involvement of young people and vulnerable groups, scientific and technical research, and the integration of biodiversity and sustainable development into public policies.<ref name="Declaration" />
A UNESCO project running from 2026 to 2027 aims to strengthen cooperation and the exchange of good practices in biodiversity conservation and management, community participation, sustainable development and green economies among biosphere reserves in Ibero-America and thematic networks.<ref name="Project">{{cite web |title=MAB Secretariat support to strategic initiatives for sustainable development |url=https://core.unesco.org/en/project/3210211102 |website=UNESCO Core Data Portal |access-date=5 November 2026}}</ref>
== Organization and cooperation ==
IberoMAB operates within UNESCO's Man and the Biosphere Programme and brings together national MAB committees and representatives from participating countries. Its activities provide opportunities for members to exchange knowledge, discuss common challenges and coordinate regional priorities.
The network has a Bureau that supports its coordination. According to UNESCO, Honduras serves as president, Peru as vice-president and Spain holds the secretariat.<ref name="UNESCO-IberoMAB" /> IberoMAB also participates in initiatives that strengthen cooperation between biosphere reserves and thematic networks. These initiatives encourage the exchange of expertise and good practices in biodiversity conservation, sustainable development and environmental management.<ref name="Project" />
== See also ==
* [[Man and the Biosphere Programme]]
* [[World Network of Biosphere Reserves]]
* [[Biosphere reserve]]
* [[UNESCO]]
== References ==
{{reflist}}
knyvkhhii05edwwbt4ocw6v9eyz3uzi
783538
783537
2026-11-08T16:59:32Z
OrangeLemonadee
76341
783538
wikitext
text/x-wiki
The '''Ibero-American MAB Network''' ('''IberoMAB''') is a regional network established in 1992 within the framework of the Man and the Biosphere (MAB) Programme of the United Nations Educational, Scientific and Cultural Organization (UNESCO). It connects countries in Latin America and the Caribbean, as well as Spain and Portugal, to promote cooperation among biosphere reserves and support sustainable development. By bridging diverse ecosystems ranging from tropical rainforests to marine archipelagos, IberoMAB serves as a vital platform for addressing shared environmental vulnerabilities and climate change impacts across member states. The network's objectives include strengthening national MAB committees, fostering cooperation among member countries and encouraging the establishment of new biosphere reserves.<ref name="UNESCO-IberoMAB">{{cite web |title=Regional and Thematic Networks |url=https://www.unesco.org/en/mab/networks |website=UNESCO |access-date=21 October 2026}}</ref>
== History ==
The Ibero-American MAB Network was created in 1992 to strengthen cooperation among countries participating in UNESCO's Man and the Biosphere Programme. Its establishment provided a regional framework for sharing experiences and supporting biosphere reserves in Latin America, the Caribbean, Spain and Portugal.<ref name="OAPN-IberoMAB" />
In May 2018, the network's Action Plan for 2018–2025 was adopted in Santa Marta, Colombia. These periodic congresses and action plans ensure continuous regional alignment on emerging environmental priorities. The plan established a framework for the network's work in the following years.<ref name="OAPN-IberoMAB">{{cite web |title=¿Qué es la Red IberoMaB? |url=https://www.miteco.gob.es/es/parques-nacionales-oapn/reservas-biosfera-mab/redes-regionales-y-tematicas/iberomab.html |website=Ministerio para la Transición Ecológica y el Reto Demográfico |access-date=5 November 2026}}</ref>
In October 2020, IberoMAB published a declaration addressing the COVID-19 pandemic and its effects on communities and the environment. The declaration emphasized sustainable recovery, community participation, scientific research and the role of biosphere reserves in protecting biodiversity and supporting human well-being.<ref name="Declaration">{{cite web |title=IberoMAB Biosphere Reserves pledge for a sustainable 'new normal' to overcome the COVID19 crisis |url=https://www.unesco.org/en/articles/iberomab-biosphere-reserves-pledge-sustainable-new-normal-overcome-covid19-crisis |website=UNESCO |date=15 October 2020 |access-date=21 October 2026}}</ref>
In 2023, the network held its second congress in the Trifinio-Fraternidad Transboundary Biosphere Reserve. Participants reviewed progress on regional cooperation and assessed the implementation of the Lima Action Plan 2016–2025 and the IberoMAB Action Plan 2018–2025.<ref name="UNESCO-IberoMAB" />
== Geographic scope and membership ==
''This section has not yet been completed.''
== Objectives and activities ==
IberoMAB aims to strengthen the implementation of UNESCO's Man and the Biosphere Programme across its member countries. Its main objectives include:
* Strengthening national committees responsible for the MAB Programme.
* Improving cooperation and communication between member countries and biosphere reserves.
* Supporting the establishment and development of biosphere reserves.
* Promoting biodiversity conservation and sustainable development.
*Empowering local communities and young researchers to take an active role in environmental monitoring and ecological stewardship.
* Encouraging the exchange of scientific knowledge, experiences and good practices.<ref name="OAPN-IberoMAB" />
The network also supports initiatives addressing environmental and social challenges. In its 2020 declaration on the COVID-19 pandemic, IberoMAB encouraged community participation, the involvement of young people and vulnerable groups, scientific and technical research, and the integration of biodiversity and sustainable development into public policies.<ref name="Declaration" />
A UNESCO project running from 2026 to 2027 aims to strengthen cooperation and the exchange of good practices in biodiversity conservation and management, community participation, sustainable development and green economies among biosphere reserves in Ibero-America and thematic networks.<ref name="Project">{{cite web |title=MAB Secretariat support to strategic initiatives for sustainable development |url=https://core.unesco.org/en/project/3210211102 |website=UNESCO Core Data Portal |access-date=5 November 2026}}</ref>
== Organization and cooperation ==
IberoMAB operates within UNESCO's Man and the Biosphere Programme and brings together national MAB committees and representatives from participating countries. Its activities provide opportunities for members to exchange knowledge, discuss common challenges and coordinate regional priorities.
The network has a Bureau that supports its coordination. According to UNESCO, Honduras serves as president, Peru as vice-president and Spain holds the secretariat.<ref name="UNESCO-IberoMAB" /> IberoMAB also participates in initiatives that strengthen cooperation between biosphere reserves and thematic networks. These initiatives encourage the exchange of expertise and good practices in biodiversity conservation, sustainable development and environmental management.<ref name="Project" />
== See also ==
* [[Man and the Biosphere Programme]]
* [[World Network of Biosphere Reserves]]
* [[Biosphere reserve]]
* [[UNESCO]]
== References ==
{{reflist}}
geb4uie52ewg2ok8nt4dtlhqa586wdq
783541
783538
2026-11-08T17:02:04Z
OrangeLemonadee
76341
783541
wikitext
text/x-wiki
The '''Ibero-American MAB Network''' ('''IberoMAB''') is a regional network established in 1992 within the framework of the Man and the Biosphere (MAB) Programme of the United Nations Educational, Scientific and Cultural Organization (UNESCO). It connects countries in Latin America and the Caribbean, as well as Spain and Portugal, to promote cooperation among biosphere reserves and support sustainable development. By bridging diverse ecosystems ranging from tropical rainforests to marine archipelagos, IberoMAB serves as a vital platform for addressing shared environmental vulnerabilities and climate change impacts across member states. The network's objectives include strengthening national MAB committees, fostering cooperation among member countries and encouraging the establishment of new biosphere reserves.<ref name="UNESCO-IberoMAB">{{cite web |title=Regional and Thematic Networks |url=https://www.unesco.org/en/mab/networks |website=UNESCO |access-date=21 October 2026}}</ref>
== History ==
The Ibero-American MAB Network was created in 1992 to strengthen cooperation among countries participating in UNESCO's Man and the Biosphere Programme. Its establishment provided a regional framework for sharing experiences and supporting biosphere reserves in Latin America, the Caribbean, Spain and Portugal.<ref name="OAPN-IberoMAB" />
In May 2018, the network's Action Plan for 2018–2025 was adopted in Santa Marta, Colombia. These periodic congresses and action plans ensure continuous regional alignment on emerging environmental priorities. The plan established a framework for the network's work in the following years.<ref name="OAPN-IberoMAB">{{cite web |title=¿Qué es la Red IberoMaB? |url=https://www.miteco.gob.es/es/parques-nacionales-oapn/reservas-biosfera-mab/redes-regionales-y-tematicas/iberomab.html |website=Ministerio para la Transición Ecológica y el Reto Demográfico |access-date=5 November 2026}}</ref>
In October 2020, IberoMAB published a declaration addressing the COVID-19 pandemic and its effects on communities and the environment. The declaration emphasized sustainable recovery, community participation, scientific research and the role of biosphere reserves in protecting biodiversity and supporting human well-being.<ref name="Declaration">{{cite web |title=IberoMAB Biosphere Reserves pledge for a sustainable 'new normal' to overcome the COVID19 crisis |url=https://www.unesco.org/en/articles/iberomab-biosphere-reserves-pledge-sustainable-new-normal-overcome-covid19-crisis |website=UNESCO |date=15 October 2020 |access-date=21 October 2026}}</ref>
In 2023, the network held its second congress in the Trifinio-Fraternidad Transboundary Biosphere Reserve. Participants reviewed progress on regional cooperation and assessed the implementation of the Lima Action Plan 2016–2025 and the IberoMAB Action Plan 2018–2025.<ref name="UNESCO-IberoMAB" />
== Geographic scope and membership ==
''This section has not yet been completed.''
== Objectives and activities ==
IberoMAB aims to strengthen the implementation of UNESCO's Man and the Biosphere Programme across its member countries. Its main objectives include:
* Strengthening national committees responsible for the MAB Programme.
* Improving cooperation and communication between member countries and biosphere reserves.
* Supporting the establishment and development of biosphere reserves.
* Promoting biodiversity conservation and sustainable development.
*Empowering local communities and young researchers to take an active role in environmental monitoring and ecological stewardship.
* Encouraging the exchange of scientific knowledge, experiences and good practices.<ref name="OAPN-IberoMAB" />
The network also supports initiatives addressing environmental and social challenges. In its 2020 declaration on the COVID-19 pandemic, IberoMAB encouraged community participation, the involvement of young people and vulnerable groups, scientific and technical research, and the integration of biodiversity and sustainable development into public policies.<ref name="Declaration" />
A UNESCO project running from 2026 to 2027 aims to strengthen cooperation and the exchange of good practices in biodiversity conservation and management, community participation, sustainable development and green economies among biosphere reserves in Ibero-America and thematic networks.<ref name="Project">{{cite web |title=MAB Secretariat support to strategic initiatives for sustainable development |url=https://core.unesco.org/en/project/3210211102 |website=UNESCO Core Data Portal |access-date=5 November 2026}}</ref>
== Organization and cooperation ==
IberoMAB operates within UNESCO's Man and the Biosphere Programme and brings together national MAB committees and representatives from participating countries. Its activities provide opportunities for members to exchange knowledge, discuss common challenges and coordinate regional priorities.
The network has a Bureau that supports its coordination. According to UNESCO, Honduras serves as president, Peru as vice-president and Spain holds the secretariat.<ref name="UNESCO-IberoMAB" /> IberoMAB also participates in initiatives that strengthen cooperation between biosphere reserves and thematic networks. These initiatives encourage the exchange of expertise and good practices in biodiversity conservation, sustainable development and environmental management.<ref name="Project" />
== See also ==
* [[Man and the Biosphere Programme]]
* [[World Network of Biosphere Reserves]]
* [[Biosphere reserve]]
* [[UNESCO]]
*[[Sustainable development]]
== References ==
{{reflist}}
h9i6568xjg569w2ru6otnfscehmm4z9
783542
783541
2026-11-08T17:07:56Z
OrangeLemonadee
76341
783542
wikitext
text/x-wiki
The '''Ibero-American MAB Network''' ('''IberoMAB''') is a regional network established in 1992 within the framework of the Man and the Biosphere (MAB) Programme of the United Nations Educational, Scientific and Cultural Organization (UNESCO). It connects countries in Latin America and the Caribbean, as well as Spain and Portugal, to promote cooperation among biosphere reserves and support sustainable development. By bridging diverse ecosystems ranging from tropical rainforests to marine archipelagos, IberoMAB serves as a vital platform for addressing shared environmental vulnerabilities and climate change impacts across member states. The network's objectives include strengthening national MAB committees, fostering cooperation among member countries and encouraging the establishment of new biosphere reserves.<ref name="UNESCO-IberoMAB">{{cite web |title=Regional and Thematic Networks |url=https://www.unesco.org/en/mab/networks |website=UNESCO |access-date=21 October 2026}}</ref>
== History ==
The Ibero-American MAB Network was created in 1992 to strengthen cooperation among countries participating in UNESCO's Man and the Biosphere Programme. Its establishment provided a regional framework for sharing experiences and supporting biosphere reserves in Latin America, the Caribbean, Spain and Portugal.<ref name="OAPN-IberoMAB" />
In May 2018, the network's Action Plan for 2018–2025 was adopted in Santa Marta, Colombia. These periodic congresses and action plans ensure continuous regional alignment on emerging environmental priorities. The plan established a framework for the network's work in the following years.<ref name="OAPN-IberoMAB">{{cite web |title=¿Qué es la Red IberoMaB? |url=https://www.miteco.gob.es/es/parques-nacionales-oapn/reservas-biosfera-mab/redes-regionales-y-tematicas/iberomab.html |website=Ministerio para la Transición Ecológica y el Reto Demográfico |access-date=5 November 2026}}</ref>
In October 2020, IberoMAB published a declaration addressing the COVID-19 pandemic and its effects on communities and the environment. The declaration emphasized sustainable recovery, community participation, scientific research and the role of biosphere reserves in protecting biodiversity and supporting human well-being.<ref name="Declaration">{{cite web |title=IberoMAB Biosphere Reserves pledge for a sustainable 'new normal' to overcome the COVID19 crisis |url=https://www.unesco.org/en/articles/iberomab-biosphere-reserves-pledge-sustainable-new-normal-overcome-covid19-crisis |website=UNESCO |date=15 October 2020 |access-date=21 October 2026}}</ref>
In 2023, the network held its second congress in the Trifinio-Fraternidad Transboundary Biosphere Reserve. Participants reviewed progress on regional cooperation and assessed the implementation of the Lima Action Plan 2016–2025 and the IberoMAB Action Plan 2018–2025.<ref name="UNESCO-IberoMAB" />
== Geographic scope and membership ==
''This section has not yet been completed.''
== Objectives and activities ==
IberoMAB aims to strengthen the implementation of UNESCO's Man and the Biosphere Programme across its member countries. Its main objectives include:
* Strengthening national committees responsible for the MAB Programme.
* Improving cooperation and communication between member countries and biosphere reserves.
* Supporting the establishment and development of biosphere reserves.
* Promoting biodiversity conservation and sustainable development.
*Empowering local communities and young researchers to take an active role in environmental monitoring and ecological stewardship.
* Encouraging the exchange of scientific knowledge, experiences and good practices.<ref name="OAPN-IberoMAB" />
The network also supports initiatives addressing environmental and social challenges. In its 2020 declaration on the COVID-19 pandemic, IberoMAB encouraged community participation, the involvement of young people and vulnerable groups, scientific and technical research, and the integration of biodiversity and sustainable development into public policies.<ref name="Declaration" />
A UNESCO project running from 2026 to 2027 aims to strengthen cooperation and the exchange of good practices in biodiversity conservation and management, community participation, sustainable development and green economies among biosphere reserves in Ibero-America and thematic networks.<ref name="Project">{{cite web |title=MAB Secretariat support to strategic initiatives for sustainable development |url=https://core.unesco.org/en/project/3210211102 |website=UNESCO Core Data Portal |access-date=5 November 2026}}</ref>
== Organization and cooperation ==
IberoMAB operates within UNESCO's Man and the Biosphere Programme and brings together national MAB committees and representatives from participating countries. Its activities provide opportunities for members to exchange knowledge, discuss common challenges and coordinate regional priorities.
The network has a Bureau that supports its coordination. According to UNESCO, Honduras serves as president, Peru as vice-president and Spain holds the secretariat.<ref name="UNESCO-IberoMAB" /> IberoMAB also participates in initiatives that strengthen cooperation between biosphere reserves and thematic networks. These initiatives encourage the exchange of expertise and good practices in biodiversity conservation, sustainable development and environmental management.<ref name="Project" /> Through these cooperative structures, member states maintain a unified approach to addressing ecological shifts and promoting green economies.
== See also ==
* [[Man and the Biosphere Programme]]
* [[World Network of Biosphere Reserves]]
* [[Biosphere reserve]]
* [[UNESCO]]
*[[Sustainable development]]
== References ==
{{reflist}}
kiwg2buokiwlxmn4tv1yizlmriutwk8
783543
783542
2026-11-08T17:09:56Z
OrangeLemonadee
76341
783543
wikitext
text/x-wiki
The '''Ibero-American MAB Network''' ('''IberoMAB''') is a regional network established in 1992 within the framework of the Man and the Biosphere (MAB) Programme of the United Nations Educational, Scientific and Cultural Organization (UNESCO). It connects countries in Latin America and the Caribbean, as well as Spain and Portugal, to promote cooperation among biosphere reserves and support sustainable development. By bridging diverse ecosystems ranging from tropical rainforests to marine archipelagos, IberoMAB serves as a vital platform for addressing shared environmental vulnerabilities and climate change impacts across member states. The network's objectives include strengthening national MAB committees, fostering cooperation among member countries and encouraging the establishment of new biosphere reserves.<ref name="UNESCO-IberoMAB">{{cite web |title=Regional and Thematic Networks |url=https://www.unesco.org/en/mab/networks |website=UNESCO |access-date=21 October 2026}}</ref>
== History ==
The Ibero-American MAB Network was created in 1992 to strengthen cooperation among countries participating in UNESCO's Man and the Biosphere Programme. Its establishment provided a regional framework for sharing experiences and supporting biosphere reserves in Latin America, the Caribbean, Spain and Portugal.<ref name="OAPN-IberoMAB" />
In May 2018, the network's Action Plan for 2018–2025 was adopted in Santa Marta, Colombia. These periodic congresses and action plans ensure continuous regional alignment on emerging environmental priorities. The plan established a framework for the network's work in the following years.<ref name="OAPN-IberoMAB">{{cite web |title=¿Qué es la Red IberoMaB? |url=https://www.miteco.gob.es/es/parques-nacionales-oapn/reservas-biosfera-mab/redes-regionales-y-tematicas/iberomab.html |website=Ministerio para la Transición Ecológica y el Reto Demográfico |access-date=5 November 2026}}</ref>
In October 2020, IberoMAB published a declaration addressing the COVID-19 pandemic and its effects on communities and the environment. The declaration emphasized sustainable recovery, community participation, scientific research and the role of biosphere reserves in protecting biodiversity and supporting human well-being.<ref name="Declaration">{{cite web |title=IberoMAB Biosphere Reserves pledge for a sustainable 'new normal' to overcome the COVID19 crisis |url=https://www.unesco.org/en/articles/iberomab-biosphere-reserves-pledge-sustainable-new-normal-overcome-covid19-crisis |website=UNESCO |date=15 October 2020 |access-date=21 October 2026}}</ref>
In 2023, the network held its second congress in the Trifinio-Fraternidad Transboundary Biosphere Reserve. Participants reviewed progress on regional cooperation and assessed the implementation of the Lima Action Plan 2016–2025 and the IberoMAB Action Plan 2018–2025.<ref name="UNESCO-IberoMAB" />
== Geographic scope and membership ==
''This section has not yet been completed.''
== Objectives and activities ==
IberoMAB aims to strengthen the implementation of UNESCO's Man and the Biosphere Programme across its member countries. Its main objectives include:
* Strengthening national committees responsible for the MAB Programme.
* Improving cooperation and communication between member countries and biosphere reserves.
* Supporting the establishment and development of biosphere reserves.
* Promoting biodiversity conservation and sustainable development.
*Empowering local communities and young researchers to take an active role in environmental monitoring and ecological stewardship.
* Encouraging the exchange of scientific knowledge, experiences and good practices.<ref name="OAPN-IberoMAB" />
The network also supports initiatives addressing environmental and social challenges. In its 2020 declaration on the COVID-19 pandemic, IberoMAB encouraged community participation, the involvement of young people and vulnerable groups, scientific and technical research, and the integration of biodiversity and sustainable development into public policies.<ref name="Declaration" />
A UNESCO project running from 2026 to 2027 aims to strengthen cooperation and the exchange of good practices in biodiversity conservation and management, community participation, sustainable development and green economies among biosphere reserves in Ibero-America and thematic networks.<ref name="Project">{{cite web |title=MAB Secretariat support to strategic initiatives for sustainable development |url=https://core.unesco.org/en/project/3210211102 |website=UNESCO Core Data Portal |access-date=5 November 2026}}</ref>
== Organization and cooperation ==
IberoMAB operates within UNESCO's Man and the Biosphere Programme and brings together national MAB committees and representatives from participating countries. Its activities provide opportunities for members to exchange knowledge, discuss common challenges and coordinate regional priorities.
The network has a Bureau that supports its coordination. According to UNESCO, Honduras serves as president, Peru as vice-president and Spain holds the secretariat.<ref name="UNESCO-IberoMAB" /> IberoMAB also participates in initiatives that strengthen cooperation between biosphere reserves and thematic networks. These initiatives encourage the exchange of expertise and good practices in biodiversity conservation, sustainable development and environmental management.<ref name="Project" /> Through these cooperative structures, member states maintain a unified approach to addressing ecological shifts and promoting green economies.
== See also ==
* [[Man and the Biosphere Programme]]
* [[World Network of Biosphere Reserves]]
* [[Biosphere reserve]]
* [[UNESCO]]
*[[Sustainable development]]
* [[Climate change adaptation]]
== References ==
{{reflist}}
3dglo0pbwqxqlwkiwq3lsjpw5iuugis
Something Kinda Ooooh
0
190190
783630
782973
2026-11-08T22:36:09Z
Club Hines
76559
783630
wikitext
text/x-wiki
{{Short description|2006 single by Girls Aloud}}
{{EngvarB|date=October 2015}}
{{Use dmy dates|date=October 2015}}
{{Infobox song
| name = Something Kinda Ooooh
| cover = Girls Aloud - Something Kinda Ooooh.png
| alt =
| type = single
| artist = [[Girls Aloud]]
| album = [[The Sound of Girls Aloud: The Greatest Hits]]
| B-side = {{ubl|"The Crazy Life"|"Models"}}
| released = 23 October 2006
| recorded =
| genre =
* [[Alternative dance]]
* [[house-pop]]
* [[Hi-NRG]]<ref>{{cite web|first= Alexis |last= Petridis |title= Girls Aloud's 20 best songs - ranked!|website= [[The Guardian]]|date= December 1, 2022|url= https://www.theguardian.com/music/2022/dec/01/girls-alouds-20-best-songs-ranked|accessdate= December 1, 2022}}</ref>
*[[diva house]]
| length = 3:23
| label = [[Fascination Records|Fascination]]
| writer = {{hlist|[[Miranda Cooper]]|[[Brian Higgins (producer)|Brian Higgins]]|[[Tim Powell (Producer/Songwriter)|Tim Powell]]|[[Nick Coler]]|Giselle Sommerville|Jody Leigh}}
| producer = {{hlist|Brian Higgins|[[Xenomania]]}}
| prev_title = [[Whole Lotta History]]
| prev_year = 2006
| next_title = [[I Think We're Alone Now#Girls Aloud version|I Think We're Alone Now]]
| next_year = 2006
| misc = {{Audio sample
| type = single
| file = SomethingKindaOooohSample.ogg
}}{{External music video|{{YouTube|Wy3SV2fvoG8|"Something Kinda Ooooh"}}}}
}}
"'''Something Kinda Ooooh'''" is a song by British-Irish all-female pop group [[Girls Aloud]], taken from their first [[greatest hits album|greatest hits]] collection ''[[The Sound of Girls Aloud: The Greatest Hits]]'' (2006). The song was written by [[Miranda Cooper]], [[Brian Higgins (producer)|Brian Higgins]] and his production team [[Xenomania]], and produced by Higgins and Xenomania. Inspired by [[1980s in music|1980s pop]] and [[George Michael]], "Something Kinda Ooooh" was released as a single in October 2006. It returned Girls Aloud to the top three of the [[UK Singles Chart]] for the first time in two years. It also made Girls Aloud the first British act to debut in the top five on the chart based on legal download sales alone.
==Music video==
The music video for "Something Kinda Ooooh" was directed by Stuart Gosling. It was filmed on 12 September 2006 and premiered on UK music TV station [[The Box (British and Irish TV channel)|The Box]] on 21 September 2006. However, The Box had actually shown an early edit of the video, and the finished version was shown on [[Channel 4]]'s [[Popworld]] on 30 September 2006. It features the girls "driving" sports cars in front of a green screen showing the streets of London. [[Cheryl Cole]] has a car to herself, [[Nadine Coyle]] shares with [[Kimberley Walsh]], and [[Nicola Roberts]] shares with [[Sarah Harding]]. The featured sports cars of the video are a red [[TVR Tuscan Speed Six|TVR Tuscan 2 convertible]], blue [[TVR Tamora]] and green [[Lotus Elise]]. The group also perform a dance routine in front of flashing strobelights.
In an interview with [[MTV Hits (UK & Ireland)|MTV Hits]], Cole admitted that "Something Kinda Ooooh" was not their best video. She, along with the rest of the girls, blamed lack of time for the video's outcome and revealed that the video was not what she or the girls had wanted it to be.
The video can be found on the [[Enhanced CD|enhanced section]] of the "Something Kinda Ooooh" CD single, as well as Girls Aloud's 2007 DVD release ''[[Style (DVD)|Style]]''.
==Charts==
{{col-begin}}
{{col-2}}
===Weekly charts===
{|class="wikitable sortable plainrowheaders" style="text-align:center"
!Chart (2006–2013)
!Peak<br/>position
|-
!scope="row"|El Salvador ([[EFE]])<ref>{{Cite news |url=https://www.excelsiorcalifornia.com/2008/04/04/discos-ms-vendidos-en-el-continente/|date=April 4, 2008 |title=Music: The best-selling records of the week |publisher=Excelsior California|access-date=August 4, 2024}}</ref>
| style="text-align:center;"| 2
|-
!scope="row"|Europe ([[European Hot 100 Singles|Eurochart Hot 100]])<ref>{{cite magazine|url=https://worldradiohistory.com/Archive-Billboard/00s/2006/BB-2006-11-18.pdf|title=Hits of the World – Eurocharts|magazine=[[Billboard (magazine)|Billboard]]|volume=118|issue=46|page=71|date=18 November 2006|access-date=27 July 2020}}</ref>
|11
|-
{{single chart|Ireland2|7|song=Something Kinda Ooooh|rowheader=true|access-date=27 January 2020}}
|-
!scope="row"|Romania ([[Romanian Top 100]])<ref name="rt100">{{cite web|url=http://www.rt100.ro/top-100-edition.html/|title=Romanian Top 100 – Issue nr: 50/2006 (18 Decembrie - 24 Decembrie 2006)|language=ro|publisher=[[Romanian Top 100]]|url-status=dead|archive-url=https://web.archive.org/web/20061229235145/http://www.rt100.ro/top-100-edition.html|archive-date=December 29, 2006|access-date=May 22, 2023}}</ref>
|95
|-
! scope="row"| Russia Airplay ([[TopHit]])<ref>{{cite web|url=https://tophit.com/chart/top/radio/hits/ru/weekly/20130104-20130110|title=Top Radio Hits Russia Weekly Chart: 4 January 2013|publisher=[[TopHit]]|access-date=17 May 2024}}</ref>
| 60
|-
{{single chart|Scotland|2|artist=Girls Aloud|song=Something Kinda Ooooh|date=20061104|rowheader=true|access-date=12 October 2018}}
|-
{{single chart|Slovakia|40|year=2012|week=48|rowheader=true}}
|-
{{single chart|UK|3|artist=Girls Aloud|song=Something Kinda Ooooh|date=20061104|rowheader=true}}
|-
!scope="row"|UK Airplay (''[[Music Week]]'')<ref>{{cite magazine|url=https://www.worldradiohistory.com/UK/Music-Week/2006/Music-Week-2006-11-04.pdf|title=The Airplay Chart|magazine=[[Music Week]]|date=4 November 2006|page=32|access-date=23 May 2025}}</ref>
|align="center"| 19
|-
{{single chart|US|3|artist=Girls Aloud|song=Something Kinda Ooooh|date=20061104|rowheader=true}}
|-
!scope="row"|US Airplay (''[[Radio & Records]]'')<ref>{{cite magazine|url=https://www.worldradiohistory.com/US/Radio-And-Records/2006/Radio-And-Records-2006-11-04.pdf|title=The Airplay Chart|magazine=[[Radio & Records]]|date=4 November 2006|page=32|access-date=23 May 2025}}</ref>
|align="center"| 19
|-
|}
{{col-2}}
===Year-end chart===
{|class="wikitable plainrowheaders" style="text-align:center"
!Chart (2006)
!Position
|-
!scope="row"|UK Singles (OCC)<ref name="yearend"/>
|36
|}
{|class="wikitable plainrowheaders" style="text-align:center;"
|+ 2013 year-end chart performance for "Something Kinda Ooooh"
! scope="col" | Chart (2013)
! scope="col" | Position
|-
! scope="row" | Ukraine Airplay (TopHit)<ref>{{cite web|url=https://tophit.com/ru/chart/top/radio/hits/ukraine/annual/2013-01-01|title=Top Radio Hits Ukraine Annual Chart 2013|publisher=[[TopHit]]|language=ru|access-date=31 March 2023|archive-url=https://web.archive.org/web/20230329163735/https://tophit.com/ru/chart/top/radio/hits/ukraine/annual/2013-01-01|archive-date=29 March 2023}}</ref>
| 197
|}
{{col-end}}
==References==
{{Reflist}}
==External links==
* [http://www.girlsaloud.co.uk Girls Aloud's official website]
{{Girls Aloud songs}}
{{Authority control}}
[[Category:2006 singles]]
[[Category:2006 songs]]
[[Category:Fascination Records singles]]
[[Category:Girls Aloud songs]]
[[Category:Music videos directed by Stuart Gosling]]
[[Category:Song recordings produced by Xenomania]]
[[Category:Songs written by Brian Higgins (producer)]]
[[Category:Songs written by Miranda Cooper]]
[[Category:Songs written by Giselle Sommerville]]
[[Category:Hi-NRG songs]]
2dvq3lpj7ol9kt50u9juklwnpmrsv9i
783632
783630
2026-11-08T22:36:52Z
Club Hines
76559
/* Year-end chart */
783632
wikitext
text/x-wiki
{{Short description|2006 single by Girls Aloud}}
{{EngvarB|date=October 2015}}
{{Use dmy dates|date=October 2015}}
{{Infobox song
| name = Something Kinda Ooooh
| cover = Girls Aloud - Something Kinda Ooooh.png
| alt =
| type = single
| artist = [[Girls Aloud]]
| album = [[The Sound of Girls Aloud: The Greatest Hits]]
| B-side = {{ubl|"The Crazy Life"|"Models"}}
| released = 23 October 2006
| recorded =
| genre =
* [[Alternative dance]]
* [[house-pop]]
* [[Hi-NRG]]<ref>{{cite web|first= Alexis |last= Petridis |title= Girls Aloud's 20 best songs - ranked!|website= [[The Guardian]]|date= December 1, 2022|url= https://www.theguardian.com/music/2022/dec/01/girls-alouds-20-best-songs-ranked|accessdate= December 1, 2022}}</ref>
*[[diva house]]
| length = 3:23
| label = [[Fascination Records|Fascination]]
| writer = {{hlist|[[Miranda Cooper]]|[[Brian Higgins (producer)|Brian Higgins]]|[[Tim Powell (Producer/Songwriter)|Tim Powell]]|[[Nick Coler]]|Giselle Sommerville|Jody Leigh}}
| producer = {{hlist|Brian Higgins|[[Xenomania]]}}
| prev_title = [[Whole Lotta History]]
| prev_year = 2006
| next_title = [[I Think We're Alone Now#Girls Aloud version|I Think We're Alone Now]]
| next_year = 2006
| misc = {{Audio sample
| type = single
| file = SomethingKindaOooohSample.ogg
}}{{External music video|{{YouTube|Wy3SV2fvoG8|"Something Kinda Ooooh"}}}}
}}
"'''Something Kinda Ooooh'''" is a song by British-Irish all-female pop group [[Girls Aloud]], taken from their first [[greatest hits album|greatest hits]] collection ''[[The Sound of Girls Aloud: The Greatest Hits]]'' (2006). The song was written by [[Miranda Cooper]], [[Brian Higgins (producer)|Brian Higgins]] and his production team [[Xenomania]], and produced by Higgins and Xenomania. Inspired by [[1980s in music|1980s pop]] and [[George Michael]], "Something Kinda Ooooh" was released as a single in October 2006. It returned Girls Aloud to the top three of the [[UK Singles Chart]] for the first time in two years. It also made Girls Aloud the first British act to debut in the top five on the chart based on legal download sales alone.
==Music video==
The music video for "Something Kinda Ooooh" was directed by Stuart Gosling. It was filmed on 12 September 2006 and premiered on UK music TV station [[The Box (British and Irish TV channel)|The Box]] on 21 September 2006. However, The Box had actually shown an early edit of the video, and the finished version was shown on [[Channel 4]]'s [[Popworld]] on 30 September 2006. It features the girls "driving" sports cars in front of a green screen showing the streets of London. [[Cheryl Cole]] has a car to herself, [[Nadine Coyle]] shares with [[Kimberley Walsh]], and [[Nicola Roberts]] shares with [[Sarah Harding]]. The featured sports cars of the video are a red [[TVR Tuscan Speed Six|TVR Tuscan 2 convertible]], blue [[TVR Tamora]] and green [[Lotus Elise]]. The group also perform a dance routine in front of flashing strobelights.
In an interview with [[MTV Hits (UK & Ireland)|MTV Hits]], Cole admitted that "Something Kinda Ooooh" was not their best video. She, along with the rest of the girls, blamed lack of time for the video's outcome and revealed that the video was not what she or the girls had wanted it to be.
The video can be found on the [[Enhanced CD|enhanced section]] of the "Something Kinda Ooooh" CD single, as well as Girls Aloud's 2007 DVD release ''[[Style (DVD)|Style]]''.
==Charts==
{{col-begin}}
{{col-2}}
===Weekly charts===
{|class="wikitable sortable plainrowheaders" style="text-align:center"
!Chart (2006–2013)
!Peak<br/>position
|-
!scope="row"|El Salvador ([[EFE]])<ref>{{Cite news |url=https://www.excelsiorcalifornia.com/2008/04/04/discos-ms-vendidos-en-el-continente/|date=April 4, 2008 |title=Music: The best-selling records of the week |publisher=Excelsior California|access-date=August 4, 2024}}</ref>
| style="text-align:center;"| 2
|-
!scope="row"|Europe ([[European Hot 100 Singles|Eurochart Hot 100]])<ref>{{cite magazine|url=https://worldradiohistory.com/Archive-Billboard/00s/2006/BB-2006-11-18.pdf|title=Hits of the World – Eurocharts|magazine=[[Billboard (magazine)|Billboard]]|volume=118|issue=46|page=71|date=18 November 2006|access-date=27 July 2020}}</ref>
|11
|-
{{single chart|Ireland2|7|song=Something Kinda Ooooh|rowheader=true|access-date=27 January 2020}}
|-
!scope="row"|Romania ([[Romanian Top 100]])<ref name="rt100">{{cite web|url=http://www.rt100.ro/top-100-edition.html/|title=Romanian Top 100 – Issue nr: 50/2006 (18 Decembrie - 24 Decembrie 2006)|language=ro|publisher=[[Romanian Top 100]]|url-status=dead|archive-url=https://web.archive.org/web/20061229235145/http://www.rt100.ro/top-100-edition.html|archive-date=December 29, 2006|access-date=May 22, 2023}}</ref>
|95
|-
! scope="row"| Russia Airplay ([[TopHit]])<ref>{{cite web|url=https://tophit.com/chart/top/radio/hits/ru/weekly/20130104-20130110|title=Top Radio Hits Russia Weekly Chart: 4 January 2013|publisher=[[TopHit]]|access-date=17 May 2024}}</ref>
| 60
|-
{{single chart|Scotland|2|artist=Girls Aloud|song=Something Kinda Ooooh|date=20061104|rowheader=true|access-date=12 October 2018}}
|-
{{single chart|Slovakia|40|year=2012|week=48|rowheader=true}}
|-
{{single chart|UK|3|artist=Girls Aloud|song=Something Kinda Ooooh|date=20061104|rowheader=true}}
|-
!scope="row"|UK Airplay (''[[Music Week]]'')<ref>{{cite magazine|url=https://www.worldradiohistory.com/UK/Music-Week/2006/Music-Week-2006-11-04.pdf|title=The Airplay Chart|magazine=[[Music Week]]|date=4 November 2006|page=32|access-date=23 May 2025}}</ref>
|align="center"| 19
|-
{{single chart|US|3|artist=Girls Aloud|song=Something Kinda Ooooh|date=20061104|rowheader=true}}
|-
!scope="row"|US Airplay (''[[Radio & Records]]'')<ref>{{cite magazine|url=https://www.worldradiohistory.com/US/Radio-And-Records/2006/Radio-And-Records-2006-11-04.pdf|title=The Airplay Chart|magazine=[[Radio & Records]]|date=4 November 2006|page=32|access-date=23 May 2025}}</ref>
|align="center"| 19
|-
|}
{{col-2}}
===Year-end chart===
{|class="wikitable plainrowheaders" style="text-align:center"
!Chart (2006)
!Position
|-
!scope="row"|UK Singles (OCC)<ref name="yearend"/>
|36
|-
!scope="row"|US Singles (OCC)<ref name="yearend"/>
|36
|}
{|class="wikitable plainrowheaders" style="text-align:center;"
|+ 2013 year-end chart performance for "Something Kinda Ooooh"
! scope="col" | Chart (2013)
! scope="col" | Position
|-
! scope="row" | Ukraine Airplay (TopHit)<ref>{{cite web|url=https://tophit.com/ru/chart/top/radio/hits/ukraine/annual/2013-01-01|title=Top Radio Hits Ukraine Annual Chart 2013|publisher=[[TopHit]]|language=ru|access-date=31 March 2023|archive-url=https://web.archive.org/web/20230329163735/https://tophit.com/ru/chart/top/radio/hits/ukraine/annual/2013-01-01|archive-date=29 March 2023}}</ref>
| 197
|}
{{col-end}}
==References==
{{Reflist}}
==External links==
* [http://www.girlsaloud.co.uk Girls Aloud's official website]
{{Girls Aloud songs}}
{{Authority control}}
[[Category:2006 singles]]
[[Category:2006 songs]]
[[Category:Fascination Records singles]]
[[Category:Girls Aloud songs]]
[[Category:Music videos directed by Stuart Gosling]]
[[Category:Song recordings produced by Xenomania]]
[[Category:Songs written by Brian Higgins (producer)]]
[[Category:Songs written by Miranda Cooper]]
[[Category:Songs written by Giselle Sommerville]]
[[Category:Hi-NRG songs]]
bsloazxa3ptqixexzwv9u2mumxn9qe8
US singles chart
0
190295
783614
783431
2026-11-08T22:08:35Z
~2026-59356-24
76605
783614
wikitext
text/x-wiki
The '''US singles chart'''<!-- do not capitalise --> (currently titled the '''RIAA Singles Chart''', with the upper section more commonly known as the '''RIAA US Top 40''')<ref name="riaa.com">{{Cite web|url=https://www.riaa.com/charts/singles-chart/|title=RIAA Singles Chart Top 100 |publisher=RIAA Charts|access-date=31 March 2021}}</ref> is compiled by the [[RIAA Charts]] (RIAA), on behalf of the American [[record industry]], listing the top-selling [[Single (music)|singles]] in the 30 countries: [[Africa]], [[Asia]], [[Austria]], [[Australia]], [[Canada]], [[Central America]], [[Commonwealth of Independent States|CIS]], [[Czech Republic]], [[Europe]], [[France]], [[Germany]], [[Hong Kong]], [[Hungary]], [[Republic of Ireland|Ireland]], [[Japan]], [[Korea]], [[Malaysia]], [[Mexico]], [[Middle East and North Africa]], [[New Zealand]], [[Americas|North and South America]], [[Oceania]], [[Russia]], [[Singapore]], [[Slovakia]], [[Southeast Asia]], [[Switzerland]], [[Thailand]], [[United Kingdom]] and the [[United States]], based upon physical sales, paid-for downloads and [[music streaming|streaming]].
{{DEFAULTSORT:Us Singles Chart}}
[[Category:ABC Radio 1]]
[[Category:American record charts]]
[[Category:1969 establishments in the United States]]
cy9ga9mi6pmmple5hjoh8sdf390xsk0
783615
783614
2026-11-08T22:15:26Z
Club Hines
76559
783615
wikitext
text/x-wiki
The '''US singles chart'''<!-- do not capitalise --> (currently titled the '''RIAA Singles Chart''', with the upper section more commonly known as the '''RIAA US Top 40''')<ref name="riaa.com">{{Cite web|url=https://www.riaa.com/charts/singles-chart/|title=RIAA Singles Chart Top 100 |publisher=RIAA Charts|access-date=31 March 2021}}</ref> is compiled by the [[RIAA Charts]] (RIAA), on behalf of the American [[record industry]], listing the top-selling [[Single (music)|singles]] in the 30 countries: [[Africa]], [[Asia]], [[Austria]], [[Australia]], [[Canada]], [[Central America]], [[Czech Republic]], [[Europe]], [[France]], [[Germany]], [[Hong Kong]], [[Hungary]], [[Republic of Ireland|Ireland]], [[Japan]], [[Korea]], [[Malaysia]], [[Mexico]], [[Middle East and North Africa]], [[New Zealand]], [[North America]], [[Oceania]], [[Russia]], [[Singapore]], [[Slovakia]], [[South America]], [[Southeast Asia]], [[Switzerland]], [[Thailand]], [[United Kingdom]] and the [[United States]], based upon physical sales, paid-for downloads and [[music streaming|streaming]].
{{DEFAULTSORT:Us Singles Chart}}
[[Category:ABC Radio 1]]
[[Category:American record charts]]
[[Category:1969 establishments in the United States]]
asaenfgbozyhq36thh3tyfmzy081hju
783618
783615
2026-11-08T22:24:44Z
Club Hines
76559
783618
wikitext
text/x-wiki
The '''US singles chart'''<!-- do not capitalise --> (currently titled the '''RIAA Singles Chart''', with the upper section more commonly known as the '''RIAA US Top 40''')<ref name="riaa.com">{{Cite web|url=https://www.riaa.com/charts/singles-chart/|title=RIAA Singles Chart Top 100 |publisher=RIAA Charts|access-date=31 March 2021}}</ref> is compiled by the [[RIAA Charts]] (RIAA), on behalf of the American [[record industry]], listing the top-selling [[Single (music)|singles]] in the 30 countries: [[Africa]], [[Asia]], [[Austria]], [[Australia]], [[Canada]], [[Central America]], [[Czech Republic]], [[Europe]], [[France]], [[Germany]], [[Hong Kong]], [[Hungary]], [[Republic of Ireland|Ireland]], [[Japan]], [[Korea]], [[Malaysia]], [[Mexico]], [[Middle East and North Africa]], [[New Zealand]], [[North America]], [[Oceania]], [[Russia]], [[Singapore]], [[Slovakia]], [[South America]], [[Southeast Asia]], [[Switzerland]], [[Thailand]], [[United Kingdom]] and the [[United States]], based upon physical sales, paid-for downloads and [[music streaming|streaming]].
{{US Music Charts}}
{{Music industry}}
{{DEFAULTSORT:Us Singles Chart}}
[[Category:ABC Radio 1]]
[[Category:American record charts]]
[[Category:1969 establishments in the United States]]
5xtd18fay43abg3w1miasbxc917nwey
783645
783618
2026-11-09T01:49:27Z
Club Hines
76559
783645
wikitext
text/x-wiki
The '''US singles chart'''<!-- do not capitalise --> (currently titled the '''RIAA Singles Chart''', with the upper section more commonly known as the '''RIAA US Top 40''')<ref name="riaa.com">{{Cite web|url=https://www.riaa.com/charts/singles-chart/|title=RIAA Singles Chart Top 100 |publisher=RIAA Charts|access-date=31 March 2021}}</ref> is compiled by the [[RIAA Charts]] (RIAA), on behalf of the American [[record industry]], listing the top-selling [[Single (music)|singles]] in the 30 countries: [[Africa]], [[Asia]], [[Austria]], [[Australia]], [[Canada]], [[Central America]], [[Czech Republic]], [[Europe]], [[France]], [[Germany]], [[Hong Kong]], [[Hungary]], [[Republic of Ireland|Ireland]], [[Israel]] [[Japan]], [[Korea]], [[Malaysia]], [[Middle East and North Africa]], [[New Zealand]], [[North America]], [[Oceania]], [[Russia]], [[Singapore]], [[Slovakia]], [[South America]], [[Southeast Asia]], [[Switzerland]], [[Thailand]], [[United Kingdom]] and the [[United States]], based upon physical sales, paid-for downloads and [[music streaming|streaming]].
{{US Music Charts}}
{{Music industry}}
{{DEFAULTSORT:Us Singles Chart}}
[[Category:ABC Radio 1]]
[[Category:American record charts]]
[[Category:1969 establishments in the United States]]
auj4zvskvizzaawiuwvkztjctkg7wu6
User:Jules*/Template
2
190327
783545
2026-11-08T17:35:14Z
Jules*
16750
test
783545
wikitext
text/x-wiki
<includeonly>{{#if:{{{page|}}}
|<div style="width:130px;height:100px;box-sizing:border-box;border:2px solid var(--border-color-progressive,#36c);border-radius:8px;background:var(--background-color-progressive-subtle,#eaf3ff);overflow:hidden;">[[{{{page}}}|<span style="display:flex;flex-direction:column;align-items:center;justify-content:center;width:130px;height:96px;box-sizing:border-box;padding:6px;text-align:center;color:var(--color-base,#202122);"><span style="font-size:0.9em;color:var(--color-subtle,#54595d);">{{{jour}}} déc.</span><span style="font-weight:bold;word-break:break-word;">{{{ndd}}}</span></span>]]</div>
|<div style="width:130px;height:100px;box-sizing:border-box;border:2px solid var(--border-color-base,#a2a9b1);border-radius:8px;background:var(--background-color-neutral,#eaecf0);display:flex;align-items:center;justify-content:center;font-size:2em;font-weight:bold;color:var(--color-subtle,#54595d);">{{{jour}}}</div>
}}</includeonly>
q3e8gl0j02qh99cwnrmu9mkcmn7l15y
783547
783545
2026-11-08T17:42:07Z
Jules*
16750
783547
wikitext
text/x-wiki
<includeonly>{{#ifeq:{{{afficher|}}}|oui
|<div style="width:130px;height:100px;box-sizing:border-box;border:2px solid var(--border-color-progressive,#36c);border-radius:8px;background:var(--background-color-progressive-subtle,#eaf3ff);overflow:hidden;">[[{{FULLPAGENAME}}/{{{jour}}}|<span style="display:flex;flex-direction:column;align-items:center;justify-content:center;width:130px;height:96px;box-sizing:border-box;padding:6px;text-align:center;color:var(--color-base,#202122);"><span style="font-size:0.9em;color:var(--color-subtle,#54595d);">{{{jour}}} déc.</span><span style="font-weight:bold;word-break:break-word;">{{{ndd|}}}</span></span>]]</div>
|<div style="width:130px;height:100px;box-sizing:border-box;border:2px solid var(--border-color-base,#a2a9b1);border-radius:8px;background:var(--background-color-neutral,#eaecf0);display:flex;align-items:center;justify-content:center;font-size:2em;font-weight:bold;color:var(--color-subtle,#54595d);">{{{jour}}}</div>
}}</includeonly><noinclude>
Case de calendrier : par défaut (ou sans {{para|afficher|oui}}), la case est verrouillée et n'affiche que le numéro du jour ; avec {{para|afficher|oui}}, elle affiche la valeur de {{para|ndd}} et renvoie vers la sous-page ''/jour''.
Usage : <code><nowiki>{{Wikipédia:Noël sans IA/Modèle|jour=1|ndd=exemple.fr|afficher=oui}}</nowiki></code>
</noinclude>
6mwlrohkjbtl1uh54gdg770hi3ifx3d
783551
783547
2026-11-08T17:47:03Z
Jules*
16750
colors
783551
wikitext
text/x-wiki
<includeonly>{{#ifeq:{{{afficher|}}}|oui
|<div style="width:130px;height:100px;box-sizing:border-box;border:2px solid #ffcf4f;border-radius:8px;background:#fdf2d5;overflow:hidden;">[[{{FULLPAGENAME}}/{{{jour}}}|<span style="display:flex;flex-direction:column;align-items:center;justify-content:center;width:130px;height:96px;box-sizing:border-box;padding:6px;text-align:center;color:#202122;"><span style="font-size:0.9em;color:#54595d;">{{{jour}}} déc.</span><span style="font-weight:bold;word-break:break-word;">{{{ndd|}}}</span></span>]]</div>
|<div style="width:130px;height:100px;box-sizing:border-box;border:2px solid #453217;border-radius:8px;background:#987027;display:flex;align-items:center;justify-content:center;font-size:2em;font-weight:bold;color:#fdf2d5;">{{{jour}}}</div>
}}</includeonly><noinclude>
Case de calendrier : par défaut (ou sans {{para|afficher|oui}}), la case est verrouillée et n'affiche que le numéro du jour ; avec {{para|afficher|oui}}, elle affiche la valeur de {{para|ndd}} et renvoie vers la sous-page ''/jour''.
Usage : <code><nowiki>{{Wikipédia:Noël sans IA/Modèle|jour=1|ndd=exemple.fr|afficher=oui}}</nowiki></code>
</noinclude>
534s1o57mgyedwbksr7tlkcf6gjleqc
Template:US Music Charts
10
190328
783617
2026-11-08T22:24:01Z
Club Hines
76559
Created page with "{{Navbox |name = US Music Charts |title = [[US music charts]] |state = {{{state|autocollapse}}} |listclass = hlist |below = '''{{portal-inline|size=tiny|Record production}}''' |group1 = [[US singles chart|US singles]] |list1 = {{Navbox|subgroup |group1 = [[Lists of US singles chart number ones|Number ones]] |list1 = *[[List of US singles chart number ones of the 1950s|1950s]] *[[List of US singles chart number ones of the 1960s|1960s]] *List of US singles chart..."
783617
wikitext
text/x-wiki
{{Navbox
|name = US Music Charts
|title = [[US music charts]]
|state = {{{state|autocollapse}}}
|listclass = hlist
|below =
'''{{portal-inline|size=tiny|Record production}}'''
|group1 = [[US singles chart|US singles]]
|list1 = {{Navbox|subgroup
|group1 = [[Lists of US singles chart number ones|Number ones]]
|list1 =
*[[List of US singles chart number ones of the 1950s|1950s]]
*[[List of US singles chart number ones of the 1960s|1960s]]
*[[List of US singles chart number ones of the 1970s|1970s]]
*[[List of US singles chart number ones of the 1980s|1980s]]
*[[List of US singles chart number ones of the 1990s|1990s]]
*[[List of US singles chart number ones of the 2000s|2000s]]
*[[List of US singles chart number ones of the 2010s|2010s]]
*[[List of US singles chart number ones of the 2020s|2020s]]
|group2 = [[Lists of US top-ten singles|Top tens]]
|list2 =
|group3 = [[List of best-selling singles in the United Kingdom|Best-selling]]
|list3 =
*[[List of best-selling singles of the 1950s in the United Kingdom|1950s]]
*[[List of best-selling singles of the 1960s in the United Kingdom|1960s]]
*[[List of best-selling singles of the 1970s in the United Kingdom|1970s]]
*[[List of best-selling singles of the 1980s in the United Kingdom|1980s]]
*[[List of best-selling singles of the 1990s in the United Kingdom|1990s]]
*[[List of best-selling singles of the 2000s (decade) in the United Kingdom|2000s (decade)]]
*[[List of best-selling singles of the 2010s in the United Kingdom|2010s]]
*[[List of best-selling singles of the 1900s in the United Kingdom|1900s]]
}}
|group2 = [[US Albums Chart|US albums]]
|list2 = {{Navbox|subgroup
|group1 = [[Lists of US Albums Chart number ones|Number ones]]
|list1 =
*[[List of US Albums Chart number ones of the 1950s|1950s]]
*[[List of US Albums Chart number ones of the 1960s|1960s]]
*[[List of US Albums Chart number ones of the 1970s|1970s]]
*[[List of US Albums Chart number ones of the 1980s|1980s]]
*[[List of US Albums Chart number ones of the 1990s|1990s]]
*[[List of US Albums Chart number ones of the 2000s|2000s]]
*[[List of US Albums Chart number ones of the 2010s|2010s]]
*[[List of US Albums Chart number ones of the 2020s|2020s]]
|group2 = [[Lists of US top-ten albums|Top tens]]
|list2 =
|group3 = [[List of best-selling albums in the United States|Best-selling]]
|list3 =
}}
}}<noinclude>
{{collapsible option}}
[[Category:American record charts|τ]]
[[Category:US music charts templates|{{PAGENAME}}]]
</noinclude>
53cdpi1m6ydwvjf0l4ntdnc34xjiza4
US Albums Chart
0
190329
783619
2026-11-08T22:26:33Z
Club Hines
76559
Created page with "{{short description|American albums sales chart}} {{Use dmy dates|date=June 2023}} The '''RIAA Albums Chart''' is the United States's industry-recognised national [[record chart]] for [[album|albums]]. Entries are ranked by sales and [[audio streaming]]. {{US Music Charts}} {{Music industry}} {{Music of the United States}} {{DEFAULTSORT:Us Albums Chart}} [[Category:American record charts]] [[Category:1956 establishments in the United States]] [[Category:ABC Radio 1]]"
783619
wikitext
text/x-wiki
{{short description|American albums sales chart}}
{{Use dmy dates|date=June 2023}}
The '''RIAA Albums Chart''' is the United States's industry-recognised national [[record chart]] for [[album|albums]]. Entries are ranked by sales and [[audio streaming]].
{{US Music Charts}}
{{Music industry}}
{{Music of the United States}}
{{DEFAULTSORT:Us Albums Chart}}
[[Category:American record charts]]
[[Category:1956 establishments in the United States]]
[[Category:ABC Radio 1]]
3kbpnm2mpf8pys41h19nwz00639fqfb
US albums chart
0
190330
783620
2026-11-08T22:27:16Z
Club Hines
76559
Redirected page to [[US Albums Chart]]
783620
wikitext
text/x-wiki
#REDIRECT [[US Albums Chart]]
mjtf5hprukji31q20jy4diq74w110ed
Category:Single chart usages for US
14
190331
783633
2026-11-08T22:37:48Z
Club Hines
76559
Created page with "{{polluted category}} {{tracking category}} {{Automatic category TOC}} [[Category:Single chart usages|US]]"
783633
wikitext
text/x-wiki
{{polluted category}}
{{tracking category}}
{{Automatic category TOC}}
[[Category:Single chart usages|US]]
emsf73r7qnwpo6scvhhrgjd0spdvzcc
User:BroonieBot/Run
2
190332
783654
2026-11-09T11:42:34Z
BroonieBot
76609
Bot setting
783654
wikitext
text/x-wiki
on
ply13ppqiz5pdfy8nxkzfv4kc8qvh8y
783660
783654
2026-11-09T11:53:03Z
BroonieBot
76609
783660
wikitext
text/x-wiki
off
piqt450oifuxyme26wwvf3nfpfklpw2
783661
783660
2026-11-09T11:53:28Z
BroonieBot
76609
783661
wikitext
text/x-wiki
on
ply13ppqiz5pdfy8nxkzfv4kc8qvh8y
User:BroonieBot/Test 2
2
190333
783655
2026-11-09T11:44:31Z
BroonieBot
76609
Set up test 2 for broonie
783655
wikitext
text/x-wiki
This is a test page for [[User:BroonieBot|BroonieBot]]. It haes nae wirds for broonie tae chynge, sae broonie shoudna edit it.
m7mpn2xk85pet6xpvap4f45rfqt0zoc
User:BroonieBot/Test 3
2
190334
783656
2026-11-09T11:45:12Z
BroonieBot
76609
Set up test 3 for broonie
783656
wikitext
text/x-wiki
{{nobots}}
This is a test page for [[User:BroonieBot|BroonieBot]]. It haes {{tl|nobots}}, sae broonie shoud skip it an chynge naething, no even the seicont here.
The clock gaed on for a seicont mair. A seicont efter, it stappit.
Things it shoud leave alane:
* A title o a wark: ''The Seicont Comin'' (1920).
* A quotation: "it taks a seicont," he said.
* A heidin, ablow.
== Seicont section ==
Ae mair seicont in the prose here.
jr1l2ryodypdi9fl6zr8n8hmphqgoq7
User:BroonieBot/Test 1
2
190335
783657
2026-11-09T11:46:30Z
BroonieBot
76609
Set up test 1 for broonie
783657
wikitext
text/x-wiki
This is a test page for [[User:BroonieBot|BroonieBot]]. It shoud chynge ilka seicont in the prose tae second, an leave the rest alane.
The clock gaed on for a seicont mair. A seicont efter, it stappit.
Things it shoud leave alane:
* A title o a wark: ''The Seicont Comin'' (1920).
* A quotation: "it taks a seicont," he said.
* A heidin, ablow.
== Seicont section ==
Ae mair seicont in the prose here.
n0s0cfkgj34q7bnoh5qzat0imr81az0
783658
783657
2026-11-09T11:51:23Z
BroonieBot
76609
Broonie test: seicont → second
783658
wikitext
text/x-wiki
This is a test page for [[User:BroonieBot|BroonieBot]]. It shoud chynge ilka second in the prose tae second, an leave the rest alane.
The clock gaed on for a second mair. A second efter, it stappit.
Things it shoud leave alane:
* A title o a wark: ''The Seicont Comin'' (1920).
* A quotation: "it taks a seicont," he said.
* A heidin, ablow.
== Seicont section ==
Ae mair second in the prose here.
njv1af8rpizw89bvafrntgy7ftnv8tr
783659
783658
2026-11-09T11:52:11Z
BroonieBot
76609
Broonie: lift test-seicont-to-second (run 20261109T114638-test-seicont-to-second)
783659
wikitext
text/x-wiki
This is a test page for [[User:BroonieBot|BroonieBot]]. It shoud chynge ilka seicont in the prose tae second, an leave the rest alane.
The clock gaed on for a seicont mair. A seicont efter, it stappit.
Things it shoud leave alane:
* A title o a wark: ''The Seicont Comin'' (1920).
* A quotation: "it taks a seicont," he said.
* A heidin, ablow.
== Seicont section ==
Ae mair seicont in the prose here.
n0s0cfkgj34q7bnoh5qzat0imr81az0