webscale #5
4 changed files with 266 additions and 67 deletions
|
@ -2,6 +2,7 @@
|
|||
import speen from "~/assets/images/speen.svg";
|
||||
import { getPageHTML } from "./wiki";
|
||||
import userscript from "./userscript";
|
||||
import { nextAnimationFrame } from "./utils";
|
||||
|
||||
function initWaiting(elem: HTMLElement) {
|
||||
// Add spinner
|
||||
|
@ -27,13 +28,11 @@ async function loadPage(page: string, elem: HTMLElement) {
|
|||
html = html.replace(/"\/wiki/gi, '"//tgstation13.org/wiki');
|
||||
|
||||
// Set as HTML content and run HTML manipulations on it
|
||||
requestAnimationFrame(() => {
|
||||
await nextAnimationFrame();
|
||||
elem.innerHTML = html;
|
||||
console.log(`${page}: processing`);
|
||||
userscript(elem, page);
|
||||
console.log(`${page}: userscript applied`);
|
||||
await nextAnimationFrame();
|
||||
await userscript(elem, page);
|
||||
elem.classList.remove("waiting");
|
||||
});
|
||||
}
|
||||
|
||||
type TabElements = { tabListItem: HTMLElement; tabContentItem: HTMLElement };
|
||||
|
@ -50,7 +49,7 @@ export default class TabManager {
|
|||
this.tabContentContainer = tabcontent;
|
||||
}
|
||||
|
||||
openTab(page: string, setActive: boolean): void {
|
||||
async openTab(page: string, setActive: boolean): void {
|
||||
// Create tab list item
|
||||
const tabListItem = document.createElement("div");
|
||||
tabListItem.className = "tab";
|
||||
|
@ -72,7 +71,7 @@ export default class TabManager {
|
|||
this.tabContentContainer.appendChild(tabContentItem);
|
||||
|
||||
// Start loading page for new tab
|
||||
loadPage(page, tabContentItem);
|
||||
await loadPage(page, tabContentItem);
|
||||
|
||||
// Create tab entry
|
||||
this.tabs[page] = { tabListItem, tabContentItem };
|
||||
|
|
196
src/darkmode.ts
196
src/darkmode.ts
|
@ -1,3 +1,5 @@
|
|||
import { nextAnimationFrame } from "./utils";
|
||||
|
||||
interface ColorRGB {
|
||||
r: number;
|
||||
g: number;
|
||||
|
@ -15,6 +17,166 @@ export enum ColorFmt {
|
|||
HEX, // #RRGGBB
|
||||
}
|
||||
|
||||
/* https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
|
||||
$$('#colors_table tbody tr')
|
||||
.map((row) => {
|
||||
const [name, hex] = Array.from(row.children).slice(-3);
|
||||
return [name.innerText.trim().replace(/\s.+/g, ''), hex.innerText.trim()];
|
||||
}).sort((a, b) => [a[0], b[0]].sort()[0] === a[0] ? -1 : 1)
|
||||
.map(([name, hex]) => `${name} : '${hex}',`)
|
||||
.join('\n')
|
||||
*/
|
||||
const namedColors = {
|
||||
aliceblue : '#f0f8ff',
|
||||
antiquewhite : '#faebd7',
|
||||
aqua : '#00ffff',
|
||||
aquamarine : '#7fffd4',
|
||||
azure : '#f0ffff',
|
||||
beige : '#f5f5dc',
|
||||
bisque : '#ffe4c4',
|
||||
black : '#000000',
|
||||
blanchedalmond : '#ffebcd',
|
||||
blue : '#0000ff',
|
||||
blueviolet : '#8a2be2',
|
||||
brown : '#a52a2a',
|
||||
burlywood : '#deb887',
|
||||
cadetblue : '#5f9ea0',
|
||||
chartreuse : '#7fff00',
|
||||
chocolate : '#d2691e',
|
||||
coral : '#ff7f50',
|
||||
cornflowerblue : '#6495ed',
|
||||
cornsilk : '#fff8dc',
|
||||
crimson : '#dc143c',
|
||||
cyan : '#00ffff',
|
||||
darkblue : '#00008b',
|
||||
darkcyan : '#008b8b',
|
||||
darkgoldenrod : '#b8860b',
|
||||
darkgray : '#a9a9a9',
|
||||
darkgreen : '#006400',
|
||||
darkgrey : '#a9a9a9',
|
||||
darkkhaki : '#bdb76b',
|
||||
darkmagenta : '#8b008b',
|
||||
darkolivegreen : '#556b2f',
|
||||
darkorange : '#ff8c00',
|
||||
darkorchid : '#9932cc',
|
||||
darkred : '#8b0000',
|
||||
darksalmon : '#e9967a',
|
||||
darkseagreen : '#8fbc8f',
|
||||
darkslateblue : '#483d8b',
|
||||
darkslategray : '#2f4f4f',
|
||||
darkslategrey : '#2f4f4f',
|
||||
darkturquoise : '#00ced1',
|
||||
darkviolet : '#9400d3',
|
||||
deeppink : '#ff1493',
|
||||
deepskyblue : '#00bfff',
|
||||
dimgray : '#696969',
|
||||
dimgrey : '#696969',
|
||||
dodgerblue : '#1e90ff',
|
||||
firebrick : '#b22222',
|
||||
floralwhite : '#fffaf0',
|
||||
forestgreen : '#228b22',
|
||||
fuchsia : '#ff00ff',
|
||||
gainsboro : '#dcdcdc',
|
||||
ghostwhite : '#f8f8ff',
|
||||
gold : '#ffd700',
|
||||
goldenrod : '#daa520',
|
||||
gray : '#808080',
|
||||
green : '#008000',
|
||||
greenyellow : '#adff2f',
|
||||
grey : '#808080',
|
||||
honeydew : '#f0fff0',
|
||||
hotpink : '#ff69b4',
|
||||
indianred : '#cd5c5c',
|
||||
indigo : '#4b0082',
|
||||
ivory : '#fffff0',
|
||||
khaki : '#f0e68c',
|
||||
lavender : '#e6e6fa',
|
||||
lavenderblush : '#fff0f5',
|
||||
lawngreen : '#7cfc00',
|
||||
lemonchiffon : '#fffacd',
|
||||
lightblue : '#add8e6',
|
||||
lightcoral : '#f08080',
|
||||
lightcyan : '#e0ffff',
|
||||
lightgoldenrodyellow : '#fafad2',
|
||||
lightgray : '#d3d3d3',
|
||||
lightgreen : '#90ee90',
|
||||
lightgrey : '#d3d3d3',
|
||||
lightpink : '#ffb6c1',
|
||||
lightsalmon : '#ffa07a',
|
||||
lightseagreen : '#20b2aa',
|
||||
lightskyblue : '#87cefa',
|
||||
lightslategray : '#778899',
|
||||
lightslategrey : '#778899',
|
||||
lightsteelblue : '#b0c4de',
|
||||
lightyellow : '#ffffe0',
|
||||
lime : '#00ff00',
|
||||
limegreen : '#32cd32',
|
||||
linen : '#faf0e6',
|
||||
magenta : '#ff00ff',
|
||||
maroon : '#800000',
|
||||
mediumaquamarine : '#66cdaa',
|
||||
mediumblue : '#0000cd',
|
||||
mediumorchid : '#ba55d3',
|
||||
mediumpurple : '#9370db',
|
||||
mediumseagreen : '#3cb371',
|
||||
mediumslateblue : '#7b68ee',
|
||||
mediumspringgreen : '#00fa9a',
|
||||
mediumturquoise : '#48d1cc',
|
||||
mediumvioletred : '#c71585',
|
||||
midnightblue : '#191970',
|
||||
mintcream : '#f5fffa',
|
||||
mistyrose : '#ffe4e1',
|
||||
moccasin : '#ffe4b5',
|
||||
navajowhite : '#ffdead',
|
||||
navy : '#000080',
|
||||
oldlace : '#fdf5e6',
|
||||
olive : '#808000',
|
||||
olivedrab : '#6b8e23',
|
||||
orange : '#ffa500',
|
||||
orangered : '#ff4500',
|
||||
orchid : '#da70d6',
|
||||
palegoldenrod : '#eee8aa',
|
||||
palegreen : '#98fb98',
|
||||
paleturquoise : '#afeeee',
|
||||
palevioletred : '#db7093',
|
||||
papayawhip : '#ffefd5',
|
||||
peachpuff : '#ffdab9',
|
||||
peru : '#cd853f',
|
||||
pink : '#ffc0cb',
|
||||
plum : '#dda0dd',
|
||||
powderblue : '#b0e0e6',
|
||||
purple : '#800080',
|
||||
rebeccapurple : '#663399',
|
||||
red : '#ff0000',
|
||||
rosybrown : '#bc8f8f',
|
||||
royalblue : '#4169e1',
|
||||
saddlebrown : '#8b4513',
|
||||
salmon : '#fa8072',
|
||||
sandybrown : '#f4a460',
|
||||
seagreen : '#2e8b57',
|
||||
seashell : '#fff5ee',
|
||||
sienna : '#a0522d',
|
||||
silver : '#c0c0c0',
|
||||
skyblue : '#87ceeb',
|
||||
slateblue : '#6a5acd',
|
||||
slategray : '#708090',
|
||||
slategrey : '#708090',
|
||||
snow : '#fffafa',
|
||||
springgreen : '#00ff7f',
|
||||
steelblue : '#4682b4',
|
||||
tan : '#d2b48c',
|
||||
teal : '#008080',
|
||||
thistle : '#d8bfd8',
|
||||
tomato : '#ff6347',
|
||||
turquoise : '#40e0d0',
|
||||
violet : '#ee82ee',
|
||||
wheat : '#f5deb3',
|
||||
white : '#ffffff',
|
||||
whitesmoke : '#f5f5f5',
|
||||
yellow : '#ffff00',
|
||||
yellowgreen : '#9acd32',
|
||||
}
|
||||
|
||||
function hsvToRgb({ h, s, v }: ColorHSV): ColorRGB {
|
||||
const i = Math.floor(h * 6);
|
||||
const f = h * 6 - i;
|
||||
|
@ -72,18 +234,30 @@ function rgbToHsv({ r, g, b }: ColorRGB): ColorHSV {
|
|||
}
|
||||
|
||||
// Hacky way to get RGB values FOR SURE!
|
||||
function nameToRGB(name: string): ColorRGB {
|
||||
const nameToRGB = function self(name: string): ColorRGB {
|
||||
if ( namedColors[name] ) {
|
||||
return parseColor(namedColors[name]);
|
||||
}
|
||||
|
||||
// Create fake div
|
||||
const fakeDiv = document.createElement("div");
|
||||
fakeDiv.style.color = name;
|
||||
document.body.appendChild(fakeDiv);
|
||||
self.fakeDiv = self.fakeDiv || (() => {
|
||||
const tmp = document.createElement("div");
|
||||
Object.assign(tmp.style, {
|
||||
position: 'fixed';
|
||||
height: 0;
|
||||
width: 0;
|
||||
top: -99;
|
||||
left: -99;
|
||||
});
|
||||
document.body.appendChild(tmp);
|
||||
return tmp;
|
||||
})();
|
||||
|
||||
self.fakeDiv.style.color = name;
|
||||
|
||||
// Get color of div
|
||||
const cs = window.getComputedStyle(fakeDiv);
|
||||
const pv = cs.getPropertyValue("color");
|
||||
|
||||
// Remove div after obtaining desired color value
|
||||
document.body.removeChild(fakeDiv);
|
||||
const cs = window.getComputedStyle(self.fakeDiv);
|
||||
const pv = cs.color; //TODO slow
|
||||
|
||||
return parseColor(pv);
|
||||
}
|
||||
|
@ -92,10 +266,10 @@ function nameToRGB(name: string): ColorRGB {
|
|||
// https://stackoverflow.com/questions/11068240/what-is-the-most-efficient-way-to-parse-a-css-color-in-javascript
|
||||
function parseColor(input: string): ColorRGB {
|
||||
// Hex format
|
||||
if (input.substr(0, 1) === "#") {
|
||||
if (input[0] === "#") {
|
||||
const collen = (input.length - 1) / 3;
|
||||
const fact = [17, 1, 0.062272][collen - 1];
|
||||
return {
|
||||
return { //TODO refactor with substring
|
||||
r: Math.round(parseInt(input.substr(1, collen), 16) * fact) / 256,
|
||||
g:
|
||||
Math.round(parseInt(input.substr(1 + collen, collen), 16) * fact) / 256,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { darken, ColorFmt, lighten } from "./darkmode";
|
||||
import searchBox from "./search";
|
||||
import { findParent } from "./utils";
|
||||
import { nextAnimationFrame, findParent } from "./utils";
|
||||
|
||||
export default function userscript(root: HTMLElement, docname: string): void {
|
||||
export default async function userscript(root: HTMLElement, docname: string): void {
|
||||
root.querySelectorAll(".mw-editsection").forEach((editLink) => {
|
||||
editLink.parentElement.removeChild(editLink);
|
||||
window.requestAnimationFrame(() => editLink.remove())
|
||||
});
|
||||
|
||||
// Darken bgcolor
|
||||
|
@ -37,6 +37,7 @@ export default function userscript(root: HTMLElement, docname: string): void {
|
|||
});
|
||||
|
||||
// Remove fixed widths
|
||||
await nextAnimationFrame();
|
||||
root.querySelectorAll("table[width]").forEach((td) => {
|
||||
td.setAttribute("width", "100%");
|
||||
});
|
||||
|
@ -47,16 +48,18 @@ export default function userscript(root: HTMLElement, docname: string): void {
|
|||
});
|
||||
|
||||
// Fixup spacing on top quotes
|
||||
root
|
||||
.querySelectorAll<HTMLImageElement>("table .floatright > a > img")
|
||||
.forEach((img) => {
|
||||
const row = findParent(img, (el) => el instanceof HTMLTableRowElement);
|
||||
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);
|
||||
});
|
||||
|
||||
// Group headers and content so stickies don't overlap
|
||||
root.querySelectorAll("h3,h2").forEach((h3) => {
|
||||
root.querySelectorAll("h3,h2").forEach((h3) => { //NOTE slow
|
||||
const parent = h3.parentNode;
|
||||
const div = document.createElement("div");
|
||||
parent.insertBefore(div, h3);
|
||||
|
@ -71,17 +74,28 @@ export default function userscript(root: HTMLElement, docname: string): void {
|
|||
});
|
||||
|
||||
// Move id from header to container, if one is found
|
||||
root.querySelectorAll<HTMLElement>(".mw-headline").forEach((span) => {
|
||||
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) {
|
||||
container.id = span.id;
|
||||
span.id += "-span";
|
||||
container.dataset.name = span.innerText;
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
// Tell user that better chemistry is loading
|
||||
const postbody = root;
|
||||
|
@ -94,32 +108,35 @@ export default function userscript(root: HTMLElement, docname: string): void {
|
|||
</table>`;
|
||||
postbody.insertBefore(statusMessage, postbody.firstChild);
|
||||
|
||||
function betterChemistry() {
|
||||
async function betterChemistry() {
|
||||
// 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
|
||||
document
|
||||
.querySelectorAll(
|
||||
const tmpTooltiptext = Array.from(document.querySelectorAll(
|
||||
"table.wikitable > tbody > tr:not(:first-child) > td:nth-child(2), .tooltiptext"
|
||||
)
|
||||
.forEach((td) => {
|
||||
))
|
||||
.map((td) => {
|
||||
const tmp = td.cloneNode() as HTMLElement;
|
||||
// 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.
|
||||
Array.from(td.childNodes).forEach((el) => {
|
||||
Array.from(td.childNodes).forEach((el) => { //TODO really slow
|
||||
if (el instanceof HTMLParagraphElement) {
|
||||
tmp.append(...el.childNodes);
|
||||
} else {
|
||||
tmp.append(el);
|
||||
}
|
||||
});
|
||||
td.parentNode.replaceChild(tmp, td);
|
||||
return [td, td.parentNode, tmp];
|
||||
});
|
||||
await nextAnimationFrame();
|
||||
for (const [td, parent, newTD] of tmpTooltiptext) {
|
||||
parent.replaceChild(newTD, td);
|
||||
}
|
||||
|
||||
// Enrich "x part" with checkboxes and parts
|
||||
Array.from(document.querySelectorAll("td"))
|
||||
.filter((el) => el.innerText.indexOf(" part") >= 0)
|
||||
.forEach((el) => {
|
||||
el.innerHTML = el.innerHTML.replace(
|
||||
const tmpParts = Array.from(document.querySelectorAll("td"))
|
||||
.filter((el) => el.textContent.indexOf(" part") >= 0)
|
||||
.map((el) => {
|
||||
const newInnerHTML = el.innerHTML.replace( //TODO slow
|
||||
/((\d+)\s+(?:parts?|units?))(.*?(?:<\/a>|\n|$))/gi,
|
||||
(match, ...m) =>
|
||||
`<label class="bgus_part ${
|
||||
|
@ -133,7 +150,12 @@ export default function userscript(root: HTMLElement, docname: string): void {
|
|||
'<span class="bgus_nobreak bgus_nested_element">$1<span class="bgus_twistie"></span></span>'
|
||||
)}`
|
||||
);
|
||||
return [el, newInnerHTML];
|
||||
});
|
||||
await nextAnimationFrame();
|
||||
for (const [el, newInnerHTML] of tmpParts) {
|
||||
el.innerHTML = newInnerHTML;
|
||||
}
|
||||
// Add event to autofill child checkboxes
|
||||
root
|
||||
.querySelectorAll(".bgus_part_tooltip > .bgus_checkbox")
|
||||
|
@ -156,7 +178,7 @@ export default function userscript(root: HTMLElement, docname: string): void {
|
|||
});
|
||||
|
||||
// Wrap every recipe with extra metadata
|
||||
root.querySelectorAll<HTMLElement>(".bgus_part").forEach((el) => {
|
||||
root.querySelectorAll<HTMLElement>(".bgus_part").forEach((el) => { //NOTE slow-ish
|
||||
if ("parts" in el.parentElement.dataset) {
|
||||
el.parentElement.dataset.parts = (
|
||||
parseInt(el.parentElement.dataset.parts, 10) +
|
||||
|
@ -220,11 +242,11 @@ export default function userscript(root: HTMLElement, docname: string): void {
|
|||
const remTable = root.querySelector(
|
||||
"#Non-craftable_Medicines + h4 + p + table"
|
||||
);
|
||||
remTable.parentElement.removeChild(remTable);
|
||||
remTable.remove();
|
||||
|
||||
root
|
||||
.querySelectorAll<HTMLElement>("div[data-name] .wikitable.sortable tr")
|
||||
.forEach((row) => {
|
||||
.forEach((row) => { //TODO slow
|
||||
const sectionEl = findParent(
|
||||
row,
|
||||
(sel) => "name" in sel.dataset && sel.dataset.name !== ""
|
||||
|
@ -238,7 +260,7 @@ export default function userscript(root: HTMLElement, docname: string): void {
|
|||
th.classList.add("table-head");
|
||||
return;
|
||||
}
|
||||
th.parentElement.removeChild(th);
|
||||
th.remove();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -285,11 +307,11 @@ export default function userscript(root: HTMLElement, docname: string): void {
|
|||
}
|
||||
title.classList.add("reagent-ext");
|
||||
title.innerHTML = content;
|
||||
if (desc) desc.parentElement.removeChild(desc);
|
||||
if (treatment) treatment.parentElement.removeChild(treatment);
|
||||
if (metabolism) metabolism.parentElement.removeChild(metabolism);
|
||||
if (overdose) overdose.parentElement.removeChild(overdose);
|
||||
if (addiction) addiction.parentElement.removeChild(addiction);
|
||||
if (desc) desc.remove();
|
||||
if (treatment) treatment.remove();
|
||||
if (metabolism) metabolism.remove();
|
||||
if (overdose) overdose.remove();
|
||||
if (addiction) addiction.remove();
|
||||
});
|
||||
|
||||
document.body.addEventListener("keydown", (ev) => {
|
||||
|
@ -335,7 +357,7 @@ export default function userscript(root: HTMLElement, docname: string): void {
|
|||
});
|
||||
}
|
||||
|
||||
function betterGeneric() {
|
||||
async function betterGeneric() {
|
||||
const el = Array.from(
|
||||
root.querySelectorAll<HTMLElement>("div.mw-headline-cont[id][data-name]")
|
||||
);
|
||||
|
@ -352,10 +374,10 @@ export default function userscript(root: HTMLElement, docname: string): void {
|
|||
|
||||
switch (docname) {
|
||||
case "Guide_to_chemistry":
|
||||
betterChemistry();
|
||||
await betterChemistry();
|
||||
break;
|
||||
default:
|
||||
betterGeneric();
|
||||
await betterGeneric();
|
||||
break;
|
||||
}
|
||||
// Everything is loaded, remove loading bar
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
export function nextAnimationFrame(): Promise {
|
||||
return new Promise(requestAnimationFrame);
|
||||
}
|
||||
|
||||
export function findParent(
|
||||
base: HTMLElement,
|
||||
matchFn: (candidate: HTMLElement) => boolean
|
||||
|
@ -12,4 +16,4 @@ export function findParent(
|
|||
return parent;
|
||||
}
|
||||
|
||||
export default { findParent };
|
||||
export default { nextAnimationFrame, findParent };
|
||||
|
|
Reference in a new issue