2020-06-16 16:21:12 +00:00
import { darken , ColorFmt , lighten } from "./darkmode" ;
2020-06-17 16:11:55 +00:00
import searchBox from "./search" ;
2020-06-20 15:13:49 +00:00
import { nextAnimationFrame , findParent } from "./utils" ;
2020-06-16 10:51:24 +00:00
2020-06-20 15:13:49 +00:00
export default async function userscript ( root : HTMLElement , docname : string ) : void {
2020-06-16 16:21:12 +00:00
root . querySelectorAll ( ".mw-editsection" ) . forEach ( ( editLink ) = > {
2020-06-20 15:13:49 +00:00
window . requestAnimationFrame ( ( ) = > editLink . remove ( ) )
2020-06-16 16:21:12 +00:00
} ) ;
// Darken bgcolor
root . querySelectorAll ( "*[bgcolor]" ) . forEach ( ( td ) = > {
let bgcolor = td . getAttribute ( "bgcolor" ) ;
// Shitty way to detect if it's hex or not
// Basically, none of the css colors long 6 letters only use hex letters
// THANK FUCKING GOD
2020-06-17 15:44:50 +00:00
if ( bgcolor . length === 6 && ! Number . isNaN ( parseInt ( bgcolor , 16 ) ) ) {
2020-06-17 13:16:54 +00:00
bgcolor = ` # ${ bgcolor } ` ;
2020-06-16 16:21:12 +00:00
}
td . setAttribute ( "bgcolor" , darken ( bgcolor , ColorFmt . HEX ) . slice ( 1 ) ) ;
} ) ;
2020-06-17 16:11:55 +00:00
root . querySelectorAll < HTMLElement > ( "*[style]" ) . forEach ( ( td ) = > {
2020-06-17 13:16:54 +00:00
if ( td . style . backgroundColor !== "" ) {
2020-06-16 16:21:12 +00:00
td . style . backgroundColor = darken ( td . style . backgroundColor , ColorFmt . RGB ) ;
}
2020-06-17 13:16:54 +00:00
if ( td . style . background !== "" ) {
2020-06-16 16:21:12 +00:00
td . style . backgroundColor = darken ( td . style . background , ColorFmt . RGB ) ;
}
} ) ;
// Lighten fgcolors
root . querySelectorAll ( "*[color]" ) . forEach ( ( td ) = > {
let color = td . getAttribute ( "color" ) ;
2020-06-17 13:16:54 +00:00
if ( color . length === 6 && ! Number . isNaN ( parseInt ( color , 16 ) ) ) {
color = ` # ${ color } ` ;
2020-06-16 16:21:12 +00:00
}
td . setAttribute ( "color" , lighten ( color , ColorFmt . HEX ) . slice ( 1 ) ) ;
} ) ;
// Remove fixed widths
2020-06-20 15:13:49 +00:00
await nextAnimationFrame ( ) ;
2020-06-16 16:21:12 +00:00
root . querySelectorAll ( "table[width]" ) . forEach ( ( td ) = > {
td . setAttribute ( "width" , "100%" ) ;
} ) ;
root . querySelectorAll ( "table[style]" ) . forEach ( ( td : HTMLTableElement ) = > {
2020-06-17 13:16:54 +00:00
if ( td . style . width !== "" ) {
2020-06-16 16:21:12 +00:00
td . style . width = "100%" ;
}
} ) ;
2020-06-18 09:49:48 +00:00
// Fixup spacing on top quotes
2020-06-20 15:13:49 +00:00
const tmpFloatRows = Array . from ( root . querySelectorAll < HTMLImageElement > ( "table .floatright > a > img" ) )
. map ( ( img ) = > {
return findParent ( img , ( el ) = > el instanceof HTMLTableRowElement ) ;
} )
await nextAnimationFrame ( ) ;
tmpFloatRows . forEach ( ( row ) = > {
const td = document . createElement ( "td" ) ;
row . appendChild ( td ) ;
} ) ;
2020-06-18 09:49:48 +00:00
2020-06-16 16:21:12 +00:00
// Group headers and content so stickies don't overlap
2020-06-20 15:13:49 +00:00
root . querySelectorAll ( "h3,h2" ) . forEach ( ( h3 ) = > { //NOTE slow
2020-06-16 16:21:12 +00:00
const parent = h3 . parentNode ;
const div = document . createElement ( "div" ) ;
parent . insertBefore ( div , h3 ) ;
while ( h3 . nextSibling && ! h3 . nextSibling . nodeName . startsWith ( "H" ) ) {
const sibling = h3 . nextSibling ;
parent . removeChild ( sibling ) ;
div . appendChild ( sibling ) ;
}
h3 . parentNode . removeChild ( h3 ) ;
div . insertBefore ( h3 , div . firstChild ) ;
div . className = "mw-headline-cont" ;
} ) ;
2020-06-18 09:49:48 +00:00
// Move id from header to container, if one is found
2020-06-20 15:13:49 +00:00
const tmpHeaders = Array . from ( root . querySelectorAll < HTMLElement > ( ".mw-headline" ) )
. map ( ( span ) = > {
// Find nearest container
const container = findParent ( span , ( el ) = >
el . classList . contains ( "mw-headline-cont" )
) ;
if ( container ) {
return [ container , span , span . id , span . textContent ] ; //NOTE slow
} else {
return null ;
}
} )
. filter ( ( e ) = > e !== null ) ;
await nextAnimationFrame ( ) ;
for ( const [ container , span , spanId , spanInnerText ] of tmpHeaders ) {
container . id = spanId ;
span . id += "-span" ;
}
await nextAnimationFrame ( ) ;
for ( const [ container , span , spanId , spanInnerText ] of tmpHeaders ) {
container . dataset . name = spanInnerText ;
}
2020-06-16 16:21:12 +00:00
2020-06-16 10:51:24 +00:00
// Tell user that better chemistry is loading
const postbody = root ;
const statusMessage = document . createElement ( "div" ) ;
statusMessage . innerHTML = `
< table style = "background-color: black; margin-bottom:10px;" width = "95%" align = "center" >
< tbody > < tr > < td align = "center" >
< b > Hang on . . . < / b > Better guides is loading .
< / td > < / tr > < / tbody >
< / table > ` ;
postbody . insertBefore ( statusMessage , postbody . firstChild ) ;
2020-06-20 15:13:49 +00:00
async function betterChemistry() {
2020-06-16 10:51:24 +00:00
// Fix inconsistencies with <p> on random parts
// Ideally I'd like a <p> or something on every part, wrapping it completely, but for now let's just kill 'em
2020-06-20 15:13:49 +00:00
const tmpTooltiptext = Array . from ( document . querySelectorAll (
"table.wikitable > tbody > tr:not(:first-child) > td:nth-child(2), .tooltiptext"
) )
. map ( ( td ) = > {
2020-06-17 13:16:54 +00:00
const tmp = td . cloneNode ( ) as HTMLElement ;
2020-06-16 19:13:18 +00:00
// The cast to Array is necessary because, while childNodes's NodeList technically has a forEach method, it's a live list and operations mess with its lenght in the middle of the loop.
// Nodes can only have one parent so append removes them from the original NodeList and shifts the following one back into the wrong index.
2020-06-20 15:13:49 +00:00
Array . from ( td . childNodes ) . forEach ( ( el ) = > { //TODO really slow
2020-06-17 16:49:38 +00:00
if ( el instanceof HTMLParagraphElement ) {
2020-06-17 16:11:55 +00:00
tmp . append ( . . . el . childNodes ) ;
} else {
tmp . append ( el ) ;
2020-06-16 19:13:18 +00:00
}
} ) ;
2020-06-20 15:13:49 +00:00
return [ td , td . parentNode , tmp ] ;
2020-06-16 10:51:24 +00:00
} ) ;
2020-06-20 15:13:49 +00:00
await nextAnimationFrame ( ) ;
for ( const [ td , parent , newTD ] of tmpTooltiptext ) {
parent . replaceChild ( newTD , td ) ;
}
2020-06-16 10:51:24 +00:00
// Enrich "x part" with checkboxes and parts
2020-06-20 15:13:49 +00:00
const tmpParts = Array . from ( document . querySelectorAll ( "td" ) )
. filter ( ( el ) = > el . textContent . indexOf ( " part" ) >= 0 )
. map ( ( el ) = > {
const newInnerHTML = el . innerHTML . replace ( //TODO slow
2020-06-16 19:13:18 +00:00
/((\d+)\s+(?:parts?|units?))(.*?(?:<\/a>|\n|$))/gi ,
2020-06-16 10:51:24 +00:00
( match , . . . m ) = >
` <label class="bgus_part ${
m [ 2 ] . includes ( "</a>" ) ? "bgus_part_tooltip" : ""
} " data-amount=" $ {
m [ 1 ]
} "><input type=" checkbox " class='bgus_checkbox bgus_hidden'/> <span class=" bgus_part_label " data-src=" $ {
m [ 0 ]
} " > $ { m [ 0 ] } < / span > < / label > $ { m [ 2 ] . replace (
/(<a .+?<\/a>)/gi ,
'<span class="bgus_nobreak bgus_nested_element">$1<span class="bgus_twistie"></span></span>'
) } `
) ;
2020-06-20 15:13:49 +00:00
return [ el , newInnerHTML ] ;
2020-06-16 10:51:24 +00:00
} ) ;
2020-06-20 15:13:49 +00:00
await nextAnimationFrame ( ) ;
for ( const [ el , newInnerHTML ] of tmpParts ) {
el . innerHTML = newInnerHTML ;
}
2020-06-16 10:51:24 +00:00
// Add event to autofill child checkboxes
2020-06-16 19:13:18 +00:00
root
2020-06-16 10:51:24 +00:00
. querySelectorAll ( ".bgus_part_tooltip > .bgus_checkbox" )
2020-06-17 13:16:54 +00:00
. forEach ( ( box : HTMLInputElement ) = > {
2020-06-16 10:51:24 +00:00
const tooltip = box . parentElement . nextElementSibling ;
2020-06-17 13:16:54 +00:00
box . addEventListener ( "click" , ( ) = > {
2020-06-16 10:51:24 +00:00
tooltip
. querySelectorAll ( ".bgus_checkbox" )
2020-06-17 13:16:54 +00:00
. forEach ( ( el : HTMLInputElement ) = > {
el . checked = box . checked ;
} ) ;
2020-06-16 10:51:24 +00:00
} ) ;
} ) ;
// Add event to collapse subsections
root . querySelectorAll ( ".bgus_nested_element" ) . forEach ( ( twistie ) = > {
2020-06-17 13:16:54 +00:00
twistie . addEventListener ( "click" , ( ) = > {
2020-06-16 19:13:18 +00:00
twistie . classList . toggle ( "bgus_collapsed" ) ;
2020-06-16 10:51:24 +00:00
} ) ;
} ) ;
// Wrap every recipe with extra metadata
2020-06-20 15:13:49 +00:00
root . querySelectorAll < HTMLElement > ( ".bgus_part" ) . forEach ( ( el ) = > { //NOTE slow-ish
2020-06-16 10:51:24 +00:00
if ( "parts" in el . parentElement . dataset ) {
2020-06-17 13:16:54 +00:00
el . parentElement . dataset . parts = (
parseInt ( el . parentElement . dataset . parts , 10 ) +
parseInt ( el . dataset . amount , 10 )
) . toString ( ) ;
2020-06-16 10:51:24 +00:00
} else {
el . parentElement . dataset . parts = el . dataset . amount ;
}
} ) ;
2020-06-17 13:16:54 +00:00
const setPartSize = ( labels , ml ) = > {
2020-06-16 10:51:24 +00:00
labels . forEach ( ( el ) = > {
const part = el . parentElement . dataset . amount ;
const total = el . parentElement . parentElement . dataset . parts ;
const amt = Math . ceil ( ml * ( part / total ) ) ;
el . innerHTML = ` ${ amt } ml ` ;
// Lookup tooltips
let next = el . parentElement . nextElementSibling ;
while ( next ) {
if ( next . classList . contains ( "tooltip" ) ) {
2020-06-17 13:16:54 +00:00
const sublabels = [ ] ;
2020-06-16 10:51:24 +00:00
next . querySelector ( ".tooltiptext" ) . childNodes . forEach ( ( ch ) = > {
if ( ch . classList && ch . classList . contains ( "bgus_part" ) ) {
sublabels . push ( ch . querySelector ( ".bgus_part_label" ) ) ;
}
} ) ;
setPartSize ( sublabels , amt ) ;
}
if ( next . classList . contains ( "bgus_part" ) ) {
// Done searching
break ;
}
next = next . nextElementSibling ;
}
} ) ;
} ;
2020-06-16 16:00:29 +00:00
root . classList . add ( "bchem" ) ;
2020-06-16 10:51:24 +00:00
// Init fuzzy search with elements
const el = Array . from (
2020-06-17 16:11:55 +00:00
root . querySelectorAll < HTMLElement > (
2020-06-16 10:51:24 +00:00
"table.wikitable > tbody > tr:not(:first-child) > th"
)
) ;
const name = el . map ( ( elem ) = > {
2020-06-17 13:16:54 +00:00
let partial = "" ;
2020-06-16 10:51:24 +00:00
elem . childNodes . forEach ( ( t ) = > {
if ( t instanceof Text ) {
2020-06-17 13:16:54 +00:00
partial += t . textContent ;
2020-06-16 10:51:24 +00:00
}
} ) ;
2020-06-17 13:16:54 +00:00
return partial . trim ( ) ;
2020-06-16 10:51:24 +00:00
} ) ;
2020-06-17 16:11:55 +00:00
const box = searchBox (
2020-06-16 10:51:24 +00:00
el ,
name . map ( ( e , i ) = > ( { id : i , str : e } ) )
) ;
2020-06-18 17:12:14 +00:00
document . body . appendChild ( box ) ;
2020-06-16 10:51:24 +00:00
2020-06-17 00:43:10 +00:00
// Remove "Removed medicines" section
2020-06-17 13:16:54 +00:00
const remTable = root . querySelector (
2020-06-17 00:43:10 +00:00
"#Non-craftable_Medicines + h4 + p + table"
) ;
2020-06-20 15:13:49 +00:00
remTable . remove ( ) ;
2020-06-17 00:43:10 +00:00
root
2020-06-17 16:11:55 +00:00
. querySelectorAll < HTMLElement > ( "div[data-name] .wikitable.sortable tr" )
2020-06-20 15:13:49 +00:00
. forEach ( ( row ) = > { //TODO slow
2020-06-18 09:49:48 +00:00
const sectionEl = findParent (
row ,
( sel ) = > "name" in sel . dataset && sel . dataset . name !== ""
) ;
2020-06-17 00:43:10 +00:00
const section = sectionEl . dataset . name ;
2020-06-17 13:16:54 +00:00
if ( row . querySelector ( "td" ) === null ) {
2020-06-17 00:43:10 +00:00
// Remove unused rows if found
2020-06-17 13:16:54 +00:00
const headers = row . querySelectorAll ( "th" ) ;
headers . forEach ( ( th , i ) = > {
2020-06-17 00:43:10 +00:00
if ( i < 2 ) {
2020-06-17 08:33:49 +00:00
th . classList . add ( "table-head" ) ;
2020-06-17 00:43:10 +00:00
return ;
}
2020-06-20 15:13:49 +00:00
th . remove ( ) ;
2020-06-17 00:43:10 +00:00
} ) ;
return ;
}
2020-06-17 13:16:54 +00:00
const rows = Array . from ( row . querySelectorAll ( "td" ) ) . slice ( 1 ) ;
2020-06-17 00:43:10 +00:00
let treatment = null ;
let desc = null ;
let metabolism = null ;
let overdose = null ;
let addiction = null ;
// Handle special cases
switch ( section ) {
case "Components" :
case "Virology Recipes" :
[ desc ] = rows ;
break ;
case "Narcotics" :
[ desc , metabolism , overdose , addiction ] = rows ;
break ;
case "Explosive Strength" :
case "Other Reagents" :
case "Mutation Toxins" :
[ desc , metabolism ] = rows ;
break ;
default :
// All fields
[ treatment , desc , metabolism , overdose , addiction ] = rows ;
}
2020-06-17 13:16:54 +00:00
const title = row . querySelector ( "th" ) ;
2020-06-17 00:43:10 +00:00
let content = ` <div class="reagent-header"> ${ title . innerHTML } </div> ` ;
if ( treatment ) {
content += ` <p class="treatment"> ${ treatment . innerHTML } </p> ` ;
}
if ( metabolism ) {
content += ` <p class="metabolism"> ${ metabolism . innerHTML } </p> ` ;
}
2020-06-17 13:16:54 +00:00
if ( addiction && addiction . innerHTML . trim ( ) !== "N/A" ) {
2020-06-17 00:43:10 +00:00
content += ` <p class="addiction"> ${ addiction . innerHTML } </p> ` ;
}
2020-06-17 13:16:54 +00:00
if ( overdose && overdose . innerHTML . trim ( ) !== "N/A" ) {
2020-06-17 00:43:10 +00:00
content += ` <p class="overdose"> ${ overdose . innerHTML } </p> ` ;
}
if ( desc ) {
content += ` <p> ${ desc . innerHTML } </p> ` ;
}
title . classList . add ( "reagent-ext" ) ;
title . innerHTML = content ;
2020-06-20 15:13:49 +00:00
if ( desc ) desc . remove ( ) ;
if ( treatment ) treatment . remove ( ) ;
if ( metabolism ) metabolism . remove ( ) ;
if ( overdose ) overdose . remove ( ) ;
if ( addiction ) addiction . remove ( ) ;
2020-06-17 00:43:10 +00:00
} ) ;
2020-06-17 13:16:54 +00:00
document . body . addEventListener ( "keydown" , ( ev ) = > {
2020-06-16 10:51:24 +00:00
if ( ev . shiftKey ) {
switch ( ev . keyCode ) {
// SHIFT+C = Toggle checkboxes
case 67 : {
root . classList . toggle ( "bgus_cbox" ) ;
root
. querySelectorAll ( ".bgus_checkbox:checked" )
2020-06-17 13:16:54 +00:00
. forEach ( ( sel : HTMLInputElement ) = > {
sel . checked = false ;
2020-06-16 10:51:24 +00:00
} ) ;
2020-06-17 13:16:54 +00:00
break ;
2020-06-16 10:51:24 +00:00
}
// SHIFT+B = Set whole size (beaker?) for parts/units
case 66 : {
2020-06-17 13:16:54 +00:00
const size = parseInt (
prompt ( "Write target ml (0 to reset)" , "90" ) ,
10
) ;
if ( Number . isNaN ( size ) || size <= 0 ) {
2020-06-16 10:51:24 +00:00
// Reset to parts/unit
root
. querySelectorAll ( ".bgus_part_label" )
2020-06-17 13:16:54 +00:00
. forEach ( ( sel : HTMLElement ) = > {
sel . innerHTML = sel . dataset . src ;
} ) ;
2020-06-16 10:51:24 +00:00
return ;
}
setPartSize (
root . querySelectorAll ( "td > .bgus_part > .bgus_part_label" ) ,
+ size
) ;
2020-06-17 13:16:54 +00:00
break ;
2020-06-16 10:51:24 +00:00
}
2020-06-17 13:16:54 +00:00
default :
// Do nothing
2020-06-16 10:51:24 +00:00
}
}
} ) ;
}
2020-06-20 15:13:49 +00:00
async function betterGeneric() {
2020-06-16 10:51:24 +00:00
const el = Array . from (
2020-06-17 16:11:55 +00:00
root . querySelectorAll < HTMLElement > ( "div.mw-headline-cont[id][data-name]" )
2020-06-16 10:51:24 +00:00
) ;
const name = el . map ( ( elem : HTMLDivElement ) = > elem . dataset . name . trim ( ) ) ;
// Init fuzzy search with headlines
2020-06-17 16:11:55 +00:00
const box = searchBox (
2020-06-16 10:51:24 +00:00
el ,
name . map ( ( e , i ) = > ( { id : i , str : e } ) ) ,
{ alignment : "start" }
) ;
2020-06-17 16:11:55 +00:00
root . appendChild ( box ) ;
2020-06-16 10:51:24 +00:00
}
2020-06-17 09:59:00 +00:00
switch ( docname ) {
case "Guide_to_chemistry" :
2020-06-20 15:13:49 +00:00
await betterChemistry ( ) ;
2020-06-17 09:59:00 +00:00
break ;
default :
2020-06-20 15:13:49 +00:00
await betterGeneric ( ) ;
2020-06-17 09:59:00 +00:00
break ;
}
// Everything is loaded, remove loading bar
statusMessage . innerHTML = "" ;
2020-06-16 10:51:24 +00:00
}