mirror of
https://github.com/torappinfo/uweb.git
synced 2024-08-14 23:54:59 +00:00
197 lines
5.7 KiB
JavaScript
197 lines
5.7 KiB
JavaScript
/**
|
||
* Try to color the cells of comparison tables based on their contents.
|
||
*
|
||
* @title Compare cells
|
||
*/
|
||
(function comparecells() {
|
||
/* Create a new IFRAME to get a "clean" Window object, so we can use its
|
||
* console. Sometimes sites (e.g. Twitter) override console.log and even
|
||
* the entire console object. "delete console.log" or "delete console"
|
||
* does not always work, and messing with the prototype seemed more
|
||
* brittle than this. */
|
||
let console = (function () {
|
||
let iframe = document.getElementById('xxxJanConsole');
|
||
if (!iframe) {
|
||
iframe = document.createElementNS('http://www.w3.org/1999/xhtml', 'iframe');
|
||
iframe.id = 'xxxJanConsole';
|
||
iframe.style.display = 'none';
|
||
|
||
(document.body || document.documentElement).appendChild(iframe);
|
||
}
|
||
|
||
return iframe && iframe.contentWindow && iframe.contentWindow.console || {
|
||
log: function () {}
|
||
};
|
||
})();
|
||
|
||
/**
|
||
* Get the text content for the given element.
|
||
*/
|
||
function getTextFromElement(element) {
|
||
/* TODO: take IMG@alt, BUTTON@value etc. into account */
|
||
return element.textContent.trim().toLowerCase();
|
||
}
|
||
|
||
/**
|
||
* Get a Uint8Array of the SHA-256 bytes for the given string.
|
||
*/
|
||
async function getSha256Bytes(string) {
|
||
try {
|
||
if (
|
||
typeof crypto === 'object' && typeof crypto.subtle === 'object' && typeof crypto.subtle.digest === 'function'
|
||
&& typeof Uint8Array === 'function'
|
||
&& typeof TextEncoder === 'function'
|
||
) {
|
||
return new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder('utf-8').encode(string)));
|
||
}
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function getColorsForValue(value) {
|
||
/* Cache the calculated values. */
|
||
getColorsForValue.cellValuesToRgb = getColorsForValue.cellValuesToRgb || {};
|
||
|
||
if (!getColorsForValue.cellValuesToRgb[value]) {
|
||
let normalizedValue = value.trim().toLowerCase();
|
||
let hash;
|
||
|
||
let yesValues = [
|
||
'✔',
|
||
'yes',
|
||
'ja',
|
||
'oui',
|
||
'si',
|
||
'sí'
|
||
];
|
||
|
||
let noValues = [
|
||
'x',
|
||
'no',
|
||
'nee',
|
||
'neen',
|
||
'nein',
|
||
'non',
|
||
'no'
|
||
];
|
||
|
||
if (yesValues.indexOf(normalizedValue) > -1) {
|
||
/* Make "Yes" cells green. */
|
||
getColorsForValue.cellValuesToRgb[value] = [ 150, 255, 32 ];
|
||
} else if (noValues.indexOf(normalizedValue) > -1) {
|
||
/* Make "No" cells green. */
|
||
getColorsForValue.cellValuesToRgb[value] = [ 238, 32, 32 ];
|
||
} else if ((shaBytes = await getSha256Bytes(normalizedValue))) {
|
||
/* Give other cells a color based on their content’s SHA
|
||
* hash to ensure “consistent random colors” every time. */
|
||
getColorsForValue.cellValuesToRgb[value] = [
|
||
shaBytes[0],
|
||
shaBytes[1],
|
||
shaBytes[2]
|
||
];
|
||
} else {
|
||
/* If the SHA hash could not be calculated, just use random
|
||
* values. These will change on every execution. */
|
||
getColorsForValue.cellValuesToRgb[value] = [
|
||
Math.random() * 255,
|
||
Math.random() * 255,
|
||
Math.random() * 255
|
||
];
|
||
}
|
||
}
|
||
|
||
/* Calculate/approximate the lightness (tweaked from “RGB to HSL”) to
|
||
* determine whether black or white text is best suited. */
|
||
let isLight = 150 < (
|
||
getColorsForValue.cellValuesToRgb[value][0] * 0.299
|
||
+ getColorsForValue.cellValuesToRgb[value][1] * 0.587
|
||
+ getColorsForValue.cellValuesToRgb[value][2] * 0.114
|
||
);
|
||
|
||
return {
|
||
backgroundColor: 'rgb(' + getColorsForValue.cellValuesToRgb[value].join(', ') + ')',
|
||
color: isLight
|
||
? 'black'
|
||
: 'white',
|
||
textShadow: isLight
|
||
? '1px 1px 3px white'
|
||
: '1px 1px 3px black'
|
||
};
|
||
}
|
||
|
||
/* The main function. */
|
||
(function execute(document) {
|
||
Array.from(document.querySelectorAll('table')).forEach(table => {
|
||
Array.from(table.tBodies).forEach(tBody => {
|
||
if (tBody.rows.length < 3) {
|
||
console.log('Compare cells: skipping table body ', tBody, ' because it only has ', tBody.rows.length, ' rows');
|
||
return;
|
||
}
|
||
|
||
Array.from(tBody.rows).forEach(tr => {
|
||
/* Determine the values. */
|
||
let cellValues = [];
|
||
let uniqueCellValues = new Set();
|
||
|
||
Array.from(tr.cells).forEach((cell, i) => {
|
||
/* Don't take the header cells into account. */
|
||
if (cell.tagName.toUpperCase() === 'TH') {
|
||
return;
|
||
}
|
||
|
||
/* Assume the first cell is a header cell, even if it is not a TH. */
|
||
if (i === 0) {
|
||
return;
|
||
}
|
||
|
||
cellValues[i] = getTextFromElement(cell);
|
||
uniqueCellValues.add(cellValues[i]);
|
||
});
|
||
|
||
/* Color (or not) the cells based on the values. */
|
||
let isFirstValue = true;
|
||
let firstValue;
|
||
cellValues.forEach(async function(cellValue, i) {
|
||
let hasTwoUniqueValues = uniqueCellValues.size == 2;
|
||
if (isFirstValue) {
|
||
firstValue = cellValue;
|
||
isFirstValue = false;
|
||
}
|
||
|
||
let backgroundColor;
|
||
let color;
|
||
let textShadow;
|
||
|
||
if (
|
||
uniqueCellValues.size == 1 ||
|
||
(hasTwoUniqueValues && cellValue === firstValue) ||
|
||
cellValue.trim() === ''
|
||
) {
|
||
backgroundColor = 'inherit';
|
||
color = 'inherit';
|
||
textShadow = 'inherit';
|
||
} else {
|
||
backgroundColor = (await getColorsForValue(cellValue)).backgroundColor;
|
||
color = (await getColorsForValue(cellValue)).color;
|
||
textShadow = (await getColorsForValue(cellValue)).textShadow;
|
||
}
|
||
|
||
tr.cells[i].style.setProperty('background-color', backgroundColor, 'important');
|
||
tr.cells[i].style.setProperty('color', color, 'important');
|
||
tr.cells[i].style.setProperty('text-shadow', textShadow, 'important');
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
/* Recurse for frames and iframes. */
|
||
try {
|
||
Array.from(document.querySelectorAll('frame, iframe, object[type^="text/html"], object[type^="application/xhtml+xml"]')).forEach(function (elem) {
|
||
execute(elem.contentDocument);
|
||
});
|
||
} catch (e) {
|
||
/* Catch exceptions for out-of-domain access, but do not do anything with them. */
|
||
}
|
||
})(document);
|
||
})();
|