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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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('&nbsp;', $('<span>').addClass('mw-collapsible-toggle-placeholder')); fieldset.$group.addClass('mw-collapsible-content'); $('#templatesandbox-editform').replaceWith(fieldset.$element.makeCollapsible()); let modules = ['ext.TemplateSandbox']; if (Number(mw.user.options.get('uselivepreview'))) { modules.push('ext.TemplateSandbox.preview'); } mw.loader.load(modules); }); mw.config.get('wgWikiID') === 'enwiki' && mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && (async () => { mw.loader.addStyleTag('.xfdnotifier-sublinks::before{content:" ["} .xfdnotifier-sublinks::after{content:"]"} .xfdnotifier-sublinks > span:not(:first-child)::before{content:"\\2009·\\2009"} .mw-portlet.vector-menu[id^="p-xfdnotifier-"] a{display:inline}'); await mw.loader.using(['mediawiki.api', 'mediawiki.Title', 'mediawiki.storage']); let xfds = [ { id: 'rm', label: 'RM', full: 'Requested moves', cat: 'Requested moves', }, { id: 'rmt', label: 'RM/T', full: 'Requested moves (technical)', page: 'Wikipedia:Requested_moves/Technical_requests', titleExtractor: $page => ( $page.find(`[data-mw*='"wt":"RMassist/core"']`).closest('li').map(function () { return this.querySelector('a[rel="mw:WikiLink"]')?.title; }).get() ) }, { id: 'afd', label: 'AfD', full: 'Articles for deletion', cat: 'Articles for deletion' }, { id: 'mfd', label: 'MfD', full: 'Miscellaneous for deletion', cat: 'Miscellaneous pages for deletion' }, { id: 'tfd', label: 'TfD', full: 'Templates for deletion', cat: 'Templates for deletion' }, { id: 'tfm', label: 'TfM', full: 'Templates for merging', cat: 'Templates for merging' }, { id: 'cfd', label: 'CfD', full: 'Categories for deletion', cat: 'Categories for deletion' }, { id: 'cfr', label: 'CfR', full: 'Categories for renaming', cat: 'Categories for renaming' }, { id: 'cfsr', label: 'CfSR', full: 'Categories for speedy renaming', cat: 'Categories for speedy renaming' }, { id: 'cfm', label: 'CfM', full: 'Categories for merging', cat: 'Categories for merging' }, { id: 'cfs', label: 'CfS', full: 'Categories for splitting', cat: 'Categories for splitting' }, { id: 'cfl', label: 'CfL', full: 'Categories for listifying', cat: 'Categories for listifying' }, { id: 'cfc', label: 'CfC', full: 'Categories for conversion', cat: 'Categories for conversion' }, { id: 'cfgd', label: 'CfGD', full: 'Categories for general discussion', cat: 'Categories for general discussion' }, { id: 'ffd', label: 'FfD', full: 'Files for discussion', cat: 'Wikipedia files for discussion' }, { id: 'rfd', label: 'RfD', full: 'Redirects for discussion', cat: 'All redirects for discussion' }, { id: 'prod', label: 'PROD', full: 'Articles proposed for deletion', cat: 'All articles proposed for deletion' } ]; window.xfd = xfds; let queryTitles = async (xfd, titles) => { if (!titles.length) return; let response = await new mw.Api().get({ action: 'query', titles: titles.slice(0, 50), prop: 'info', inprop: 'watched', formatversion: 2 }); response?.query?.pages?.forEach(p => { if (p.watched) { xfd.pages.push(p.title); } }); await queryTitles(xfd, titles.slice(50)); }; let queryPage = async xfd => { let $page = $($.parseHTML(await 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]] &#124; [[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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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('&', '&amp;'); 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