These are both easy fixes, actually, so I did go ahead and make those changes. Version 3.5 (below) Works For Me(tm) on both http:// and https://, though obviously I still recommend using https:// .
This was trickier. The problem is that textarea elements have both a "rows" attribute and the usual CSS styling; the script set "rows" to 34 unconditionally (32 for a symmetric soup + 1 for the RLE header + 1 to avoid a scrollbar), but properties influencing the textarea's height got largely ignored.
I've now implemented a cludge that limits the sample soup overlay's size to a maximum of 65% of the browser window's inner height (i.e. the height of the content, excluding the browser's own UI). This gives you a reasonably-sized overlay on very short browser windows while still giving it room to grow to the full size on larger screens.
Forcing it this way feels ugly to me -- there should be a way to simply tell the element "don't grow beyond your parent" --, but at least it works. (For the record, setting the max-height property to "100% does not.)
Code: Select all
// ==UserScript==
// @name Catagolue Reloaded
// @namespace None
// @description Various useful tweaks to Catagolue object pages.
// @include *://catagolue.appspot.com/object/*
// @version 3.5
// @grant none
// ==/UserScript==
// "On second thought, let's not hack in Javascript. 'tis a silly language."
// separator used for breadcrumb navigation links
var breadcrumbSeparator = " » "; // > ›
// MAIN function.
function MAIN() {
// read page parameters
var params = readParams();
if(params != null) {
// do our work.
addNavLinks (params);
handleSampleSoups(params);
objectToRLE (params);
} else {
// this shouldn't happen on pages where this script actually runs.
console.log("Could not read page parameters.");
}
}
/*********************************
* HTML-related helper functions *
*********************************/
// find the heading containing the object's code
function findTitleHeading() {
// find the content div; the heading is (currently) its first child.
var content = document.getElementById("content");
if(content)
return content.firstElementChild;
// this shouldn't happen unless the page layout changes.
return null;
}
// find the H2 beginning the comments section.
function findCommentsH2() {
var contentRegex = /Comments \(/;
// most elements on Catagolue pages do not have ids etc., so instead we
// look for the right h2 tag.
// NOTE: this may break if Catagolue's page layout changes.
var h2s = document.getElementsByTagName("h2");
for(var i = 0; i < h2s.length; i++) {
if(contentRegex.test(h2s[i].textContent)) {
return h2s[i];
}
}
// not found?
return null;
}
// find the paragraph containing the sample soup links.
function findSampleSoupsParagraph() {
// most elements on Catagolue pages do not have ids etc., so instead we
// look for the right h3 tag; the paragraph we want is the following
// element.
// NOTE: this may break if Catagolue's page layout changes.
var h3s = document.getElementsByTagName("h3");
for(var i = 0; i < h3s.length; i++) {
if(h3s[i].textContent == "Sample occurrences") {
return h3s[i].nextElementSibling;
}
}
// not found?
return null;
}
// append a table row containing a hr element to a node (a table, in practice).
function appendHR(node) {
var hrRow = document.createElement("tr");
var hrCell = document.createElement("td");
var hr = document.createElement("hr");
// HACK: hardcoded colspan=3.
hrCell.colSpan = "3";
hrCell.style.paddingTop = "0";
hrCell.style.paddingBottom = "0"
hr. style.margin = "0";
node. appendChild(hrRow);
hrRow. appendChild(hrCell);
hrCell.appendChild(hr);
}
// read and return apgcode, rulestring etc.
function readParams() {
// regular expression to extract apgcode and rulestring from the page URL
var locRegex = /object\/(.*?)\/(.*)/;
// regular expression to extract prefix and encoded object from apgcode
var codeRegex = /^(.*?)_(.*)$/;
var matches = locRegex.exec(document.location);
if(matches) {
var params = new Object;
// parameters extracted from URL go here.
params["apgcode"] = matches[1];
// the rulestring may or may not contain the symmetry as well.
// Normally it won't, but if the user came from a page that our
// symmetry injector script ran on, it will.
if(matches[2].indexOf("/") == -1) {
params["rule" ] = matches[2];
params["symmetry"] = null;
} else {
var pieces = matches[2].split("/", 2);
params["rule" ] = pieces[0];
params["symmetry"] = pieces[1];
}
// pathologicals do not have an object code apart from the prefix
// itself.
if(matches[1] == "PATHOLOGICAL") {
params["prefix"] = "PATHOLOGICAL";
params["object"] = "";
} else {
// separate prefix from code proper.
// FIXME: shouldn't simply assume this succeeds, I guess.
var pieces = codeRegex.exec(matches[1]);
params["prefix" ] = pieces[1];
params["object" ] = pieces[2];
}
// other parameters go here.
// (none yet)
return params;
}
// location didn't match.
return null;
}
// create and return a hyperlink
function makeLink(linkTarget, linkText) {
// create a new "a" element
var link = document.createElement("a");
link.href = linkTarget;
link.textContent = linkText;
return link;
}
/****************************************
* General GoL-related helper functions *
****************************************/
// return an empty universe of the desired size
function emptyUniverse(bx, by) {
// there's no autovivification.
var universe = new Array(bx);
for (var i = 0; i < bx; i++) {
universe[i] = new Array(by);
}
return universe;
}
// convert a rulestring to slashed uppercase notation, e.g. "B3/S23" instead
// of "b3s23" etc. Note that named rules (e.g. "tlife") are left alone, as are
// neighborhood conditions in non-totalistic rules in Hensel notation.
function ruleSlashedUpper(rule) {
rule = rule.replace(new RegExp("b", "g"), "B");
rule = rule.replace(new RegExp("s", "g"), "/S");
// we may have introduced double slashes.
rule = rule.replace(new RegExp("//", "g"), "/");
return rule;
}
// debugging function: return a pattern object as a string, suitable for
// visual inspection (e.g. using console.log).
function patternToString(patternObject) {
// string to return
var strPattern = "";
// read pattern line by line
for(var i = 0; i <= patternObject["by"]; i++) {
for(var j = 0; j <= patternObject["bx"]; j++) {
// live cells are represented by an O, dead ones by a .
if(patternObject["pattern"][j][i])
strPattern += "O";
else
strPattern += ".";
}
// add a linebreak at the end of each pattern line
strPattern += "\n";
}
return strPattern;
}
/************************************
* apgcode-related helper functions *
************************************/
// decode w/x/y in an apgcode.
// FIXME: there's got to be a more elegant/idiomatic way of doing this.
function apgcodeDecodeWXY(code) {
// replace y0 to y9 with 4 to 13 zeroes, respectively.
for(var i = 0; i <= 9; i++) {
code = code.replace(new RegExp("y" + i.toString(), "g"), "0".repeat(i + 4));
}
// replace ya to yz with 14 to 39 zeroes, respectively.
// NOTE: 97=ord('a'); 122=ord('z').
for(var i = 97; i <= 122; i++) {
code = code.replace(new RegExp("y" + String.fromCharCode(i), "g"), "0".repeat(i - 83));
}
// finally, replace w and x with 2 and 3 zeroes, respectively.
// NOTE: this needs to come last so yw and yx will be handled correctly.
code = code.replace(new RegExp("w", "g"), "00");
code = code.replace(new RegExp("x", "g"), "000");
return code;
}
// Convert an object (represented by its apgcode) to a pattern.
function apgcodeToPattern(object, rule) {
// create a 40x40 array to hold the pattern. Note that 40x40 is the
// maximum object size; larger objects are classified as PATHOLOGICAL on
// Catagolue.
var pattern = emptyUniverse(40, 40);
// decode w/x/y
object = apgcodeDecodeWXY(object);
// split object's apgcode into strips.
var strips = object.split("z");
// bounding box; this is computed en passant.
var bx = 0;
var by = 0;
for(var i = 0; i < strips.length; i++) {
// split strip into characters.
var characters = strips[i].split("");
for(var j = 0; j < characters.length; j++) {
var charCode = characters[j].charCodeAt(0);
// decode character. Letters a-v denote numbers 10-31.
var number = 0;
if((charCode >= 48) && (charCode <= 57)) {
number = charCode - 48;
} else if((charCode >= 97) && (charCode <= 118)) {
number = charCode - 87;
}
// each character encodes five bits.
for(var bit = 0; bit <= 4; bit++) {
var x = j;
var y = i * 5 + bit;
// If a bit is set...
if(number & (Math.pow(2, bit))) {
// take note of bounding box.
if(x > bx)
bx = x;
if(y > by)
by = y;
// and set the cell for this bit.
pattern[x][y] = 1;
}
}
}
}
var ret = new Object();
ret["pattern"] = pattern;
ret["bx" ] = bx;
ret["by" ] = by;
ret["rule" ] = rule;
return ret;
}
/********************************
* RLE-related helper functions *
********************************/
// return an encoded RLE run.
function RLEAddRun(count, state) {
var ret = "";
if(count > 1)
ret += count.toString();
// dead cells are encoded as "b", live cells as "o".
if(state == 1)
ret += "o";
else
ret += "b";
return ret;
}
// convert a pattern to an RLE string.
function patternToRLE(patternObject) {
// extract values
var pattern = patternObject["pattern"];
var bx = patternObject["bx"];
var by = patternObject["by"];
var rule = patternObject["rule"];
// RLE pattern
// the first line is a header.
var RLE = "x = " + (bx + 1) + ", y = " + (by + 1) + ", rule = " + ruleSlashedUpper(rule) + "\n";
// state of the ongoing run
var currentState = "NONE";
var runCount = 0;
var currentLine = "";
// read pattern linewise
for(var i = 0; i <= by; i++) {
for(var j = 0; j <= bx; j++) {
// current cell we're looking at
var cell = pattern[j][i];
// did we change state?
if(cell != currentState) {
// if our line's getting too long, flush it.
// FIXME: this may actually produce lines slightly longer
// than 70 chars. Not a problem in practice, but strictly
// speaking a violation of the spec.
if(currentLine.length >= 70) {
RLE += currentLine + "\n";
currentLine = "";
}
// if we have an ongoing run, wrap that up.
if(currentState != "NONE")
currentLine += RLEAddRun(runCount, currentState);
// begin a new run
currentState = cell;
runCount = 1;
} else
// continue ongoing run
runCount++;
}
// wrap up current run.
currentLine += RLEAddRun(runCount, currentState);
// reset run.
runCount = 0;
currentState = "NONE";
// if this isn't the last line, begin a new one.
if(i < by)
currentLine += "$";
}
// wrap up RLE
RLE += currentLine + "!\n";
return RLE;
}
/***********************
* Major functionality *
***********************/
/*** INJECTED MD5 SCRIPT ***/
/***************************************************************************
* NOTE: all code in this script was written by Paul Johnson and is taken *
* from http://pajhome.org.uk/crypt/md5/md5.html . The code is licensed *
* under the 3-clause BSD license, which is compatible with the GNU GPL. *
* See http://pajhome.org.uk/site/legal.html#bsdlicense , as well as the *
* FSF's https://www.gnu.org/licenses/license-list.html#ModifiedBSD . *
***************************************************************************/
var MD5Script = `
// change this in case Catagolue moves.
var catagolueHostName = "catagolue.appspot.com";
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
function hex_hmac_md5(k, d)
{ return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_md5(k, d)
{ return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_md5(k, d, e)
{ return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
/*
* Perform a simple self-test to see if the VM is working
*/
function md5_vm_test()
{
return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
}
/*
* Calculate the MD5 of a raw string
*/
function rstr_md5(s)
{
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}
/*
* Calculate the HMAC-MD5, of a key and some data (raw strings)
*/
function rstr_hmac_md5(key, data)
{
var bkey = rstr2binl(key);
if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}
/*
* Convert a raw string to a hex string
*/
function rstr2hex(input)
{
try { hexcase } catch(e) { hexcase=0; }
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var output = "";
var x;
for(var i = 0; i < input.length; i++)
{
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F)
+ hex_tab.charAt( x & 0x0F);
}
return output;
}
/*
* Convert a raw string to a base-64 string
*/
function rstr2b64(input)
{
try { b64pad } catch(e) { b64pad=''; }
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var output = "";
var len = input.length;
for(var i = 0; i < len; i += 3)
{
var triplet = (input.charCodeAt(i) << 16)
| (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
| (i + 2 < len ? input.charCodeAt(i+2) : 0);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > input.length * 8) output += b64pad;
else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
}
}
return output;
}
/*
* Convert a raw string to an arbitrary string encoding
*/
function rstr2any(input, encoding)
{
var divisor = encoding.length;
var i, j, q, x, quotient;
/* Convert to an array of 16-bit big-endian values, forming the dividend */
var dividend = Array(Math.ceil(input.length / 2));
for(i = 0; i < dividend.length; i++)
{
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
}
/*
* Repeatedly perform a long division. The binary array forms the dividend,
* the length of the encoding is the divisor. Once computed, the quotient
* forms the dividend for the next step. All remainders are stored for later
* use.
*/
var full_length = Math.ceil(input.length * 8 /
(Math.log(encoding.length) / Math.log(2)));
var remainders = Array(full_length);
for(j = 0; j < full_length; j++)
{
quotient = Array();
x = 0;
for(i = 0; i < dividend.length; i++)
{
x = (x << 16) + dividend[i];
q = Math.floor(x / divisor);
x -= q * divisor;
if(quotient.length > 0 || q > 0)
quotient[quotient.length] = q;
}
remainders[j] = x;
dividend = quotient;
}
/* Convert the remainders to the output string */
var output = "";
for(i = remainders.length - 1; i >= 0; i--)
output += encoding.charAt(remainders[i]);
return output;
}
/*
* Encode a string as utf-8.
* For efficiency, this assumes the input is valid utf-16.
*/
function str2rstr_utf8(input)
{
var output = "";
var i = -1;
var x, y;
while(++i < input.length)
{
/* Decode utf-16 surrogate pairs */
x = input.charCodeAt(i);
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
{
x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
i++;
}
/* Encode output as utf-8 */
if(x <= 0x7F)
output += String.fromCharCode(x);
else if(x <= 0x7FF)
output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
0x80 | ( x & 0x3F));
else if(x <= 0xFFFF)
output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
else if(x <= 0x1FFFFF)
output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
0x80 | ((x >>> 12) & 0x3F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
}
return output;
}
/*
* Encode a string as utf-16
*/
function str2rstr_utf16le(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
(input.charCodeAt(i) >>> 8) & 0xFF);
return output;
}
function str2rstr_utf16be(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
input.charCodeAt(i) & 0xFF);
return output;
}
/*
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*/
function rstr2binl(input)
{
var output = Array(input.length >> 2);
for(var i = 0; i < output.length; i++)
output[i] = 0;
for(var i = 0; i < input.length * 8; i += 8)
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
return output;
}
/*
* Convert an array of little-endian words to a string
*/
function binl2rstr(input)
{
var output = "";
for(var i = 0; i < input.length * 32; i += 8)
output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
return output;
}
/*
* Calculate the MD5 of an array of little-endian words, and a bit length.
*/
function binl_md5(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return Array(a, b, c, d);
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5_cmn(q, a, b, x, s, t)
{
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
`
/*** End of Paul Johnson's MD5 code ***/
/*** ELEMENT DRAG AND DROP SUPPORT SCRIPT ***/
/*****************************************************************************
* NOTE: script taken from http://www.quirksmode.org/js/dragdrop.html , with *
* the following changes: *
* a) removed keyboard dragging. *
* b) added ability to ignore mouse clicks in a specified child. *
* Event handlers taken from http://www.quirksmode.org/js/eventSimple.html . *
* Both written by ppk Peter-Paul Koch; no license given, but implied to be *
* permissive. *
*****************************************************************************/
var dragAndDropSupportScript = `
function addEventSimple(obj,evt,fn) {
if (obj.addEventListener)
obj.addEventListener(evt,fn,false);
else if (obj.attachEvent)
obj.attachEvent('on'+evt,fn);
}
function removeEventSimple(obj,evt,fn) {
if (obj.removeEventListener)
obj.removeEventListener(evt,fn,false);
else if (obj.detachEvent)
obj.detachEvent('on'+evt,fn);
}
dragDrop = {
initialMouseX: undefined,
initialMouseY: undefined,
startX: undefined,
startY: undefined,
draggedObject: undefined,
excludedObject: undefined,
initElement: function (element, excl) {
if (typeof element == 'string')
element = document.getElementById(element);
if (typeof excl == 'string')
excl = document.getElementById(excl);
element.onmousedown = dragDrop.startDragMouse;
excludedObject = excl;
},
startDragMouse: function (e) {
if(e.target == excludedObject)
return true;
dragDrop.startDrag(this);
var evt = e || window.event;
dragDrop.initialMouseX = evt.clientX;
dragDrop.initialMouseY = evt.clientY;
addEventSimple(document,'mousemove',dragDrop.dragMouse);
addEventSimple(document,'mouseup',dragDrop.releaseElement);
return false;
},
startDrag: function (obj) {
if (dragDrop.draggedObject)
dragDrop.releaseElement();
dragDrop.startX = obj.offsetLeft;
dragDrop.startY = obj.offsetTop;
dragDrop.draggedObject = obj;
obj.className += ' dragged';
},
dragMouse: function (e) {
var evt = e || window.event;
var dX = evt.clientX - dragDrop.initialMouseX;
var dY = evt.clientY - dragDrop.initialMouseY;
dragDrop.setPosition(dX,dY);
return false;
},
setPosition: function (dx,dy) {
dragDrop.draggedObject.style.left = dragDrop.startX + dx + 'px';
dragDrop.draggedObject.style.top = dragDrop.startY + dy + 'px';
},
releaseElement: function() {
removeEventSimple(document,'mousemove',dragDrop.dragMouse);
removeEventSimple(document,'mouseup',dragDrop.releaseElement);
dragDrop.draggedObject.className = dragDrop.draggedObject.className.replace(/dragged/,'');
dragDrop.draggedObject = null;
}
}
`
/*** INJECTED SAMPLE SOUP OVERLAY SCRIPT ***/
var sampleSoupOverlayScript = `
// Event handler to close sample soup overlay. Based on (with modifications)
// https://stackoverflow.com/posts/3369743/revisions .
function closeSoupOverlay(evt) {
evt = evt || window.event;
var isEscape = false;
var isClick = false;
if("key" in evt)
isEscape = (evt.key == "Escape");
else
isEscape = (evt.keyCode == 27);
if("type" in evt)
isClick = (evt.type == "mousedown");
if(isEscape || isClick) {
var overlayDiv = document.getElementById("sampleSoupOverlay");
if(overlayDiv)
overlayDiv.parentNode.removeChild(overlayDiv);
}
}
function overlaySoup(soupURL, soupNumber, totalSoups) {
// regex to extract soup seed etc. from soupURL
// FIXME: using \d instead of [0-9] does not work. Why?
var soupURLRegex = new RegExp("^(https?://)" + catagolueHostName + "/hashsoup/(.*?)/((m_|n_)?[A-Za-z0-9]{12})([0-9]*)/(.*)$");
// match regex against soup URL. This also verifies that we're not getting
// passed just any URL to load remotely.
var matches = soupURLRegex.exec(soupURL);
if(!matches) {
// URL could not be parsed
console.log("Could not parse soup URL: " + soupURL);
return false;
}
// collect soup parameters.
// NOTE: seedPrefix would indicate if the haul was submitted using
// apgsearch 0.x/1.x (empty string), apgnano 2.x ("n_") or apgmera 3.x
// ("m_"), but we have no use for this at the moment.
var URLScheme = matches[1]
var symmetry = matches[2];
var seed = matches[3];
// var seedPrefix = matches[4];
var soupNumberInHaul = matches[5];
var rule = matches[6];
// URL for the haul containing this soup.
var haulURL = URLScheme + catagolueHostName + "/haul/" + rule + "/" + symmetry + "/" + hex_md5(seed);
var color = symmetryColors[symmetry] || "black";
// Sample soup overlay, based on (with modifications) Method 5 on
// http://www.vikaskbh.com/five-css-techniques-make-overlay-div-centered/
// create the elements we'll need.
var overlayDiv = document.createElement("div");
var overlayInnerDiv = document.createElement("div");
var overlayShadingDiv = document.createElement("div");
var introParagraph = document.createElement("p");
var haulLink = document.createElement("a");
var soupSelectAll = document.createElement("p");
var soupSelectAllLink = document.createElement("a");
var sampleSoupTextarea = document.createElement("textarea");
// style outer div.
overlayDiv.id = "sampleSoupOverlay";
overlayDiv.style.position = "fixed";
overlayDiv.style.top = "0";
overlayDiv.style.left = "0";
overlayDiv.style.width = "100%";
overlayDiv.style.zIndex = "1";
overlayDiv.style.height = "100%";
// style inner div.
// NOTE: margin-left must be half of width for the div to be centered.
// NOTE 2: border color matches the color of the sample soup dots for
// this symmetry.
// NOTE 3: #003040 is a shade of deep cerulean, taken from Catagolue's
// background image.
overlayInnerDiv.style.position = "relative";
overlayInnerDiv.style.left = "50%";
overlayInnerDiv.style.marginLeft = "-375px";
overlayInnerDiv.style.border = "5px outset " + color;
overlayInnerDiv.style.borderRadius = "10px";
overlayInnerDiv.style.backgroundColor = "white";
overlayInnerDiv.style.color = "black";
overlayInnerDiv.style.width = "750px";
overlayInnerDiv.style.zIndex = "3";
overlayInnerDiv.style.top = "20%";
overlayInnerDiv.style.minHeight = "50%";
overlayInnerDiv.style.maxHeight = "90%";
overlayInnerDiv.style.padding = "1em";
overlayInnerDiv.style.boxShadow = "10px -10px 10px 0px #003040";
// style the div that will shade the remainder of the page behind the
// sample soup while the overlay is open.
overlayShadingDiv.style.position = "fixed";
overlayShadingDiv.style.width = "100%";
overlayShadingDiv.style.height = "100%";
overlayShadingDiv.style.margin = "auto";
overlayShadingDiv.style.backgroundColor = "black";
overlayShadingDiv.style.opacity = "0.5";
overlayShadingDiv.style.zIndex = "2";
overlayShadingDiv.style.top = "0";
overlayShadingDiv.style.left = "0";
// clicking outside the overlay will close it.
overlayShadingDiv.onmousedown = closeSoupOverlay;
// link to the haul containing this soup
haulLink.href = haulURL;
haulLink.textContent = "Haul";
// a short introductory note informing the user which soup this is.
// NOTE: U+2116 is the "Numero" symbol.
introParagraph.style.marginTop = "0";
introParagraph.appendChild(document.createTextNode(symmetry + " soup \u2116 " + soupNumber.toString() + " / " + totalSoups.toString() + " ("));
introParagraph.appendChild(haulLink);
introParagraph.appendChild(document.createTextNode(")"));
// create "select all" link for the sample soup
soupSelectAll.style.marginTop = 0;
soupSelectAll.style.marginBottom = "0.5em";
soupSelectAll.style.fontFamily = "monospace";
soupSelectAllLink.href = "#";
soupSelectAllLink.textContent = "Select All";
soupSelectAllLink.setAttribute("onclick", 'document.getElementById("sampleSoupTextArea").select(); return false');
soupSelectAll.appendChild(soupSelectAllLink);
// the textarea that will hold the soup.
sampleSoupTextarea.id = "sampleSoupTextArea";
sampleSoupTextarea.style.width = "100%";
sampleSoupTextarea.style.overflowY = "scroll";
sampleSoupTextarea.rows = "34";
sampleSoupTextarea.style.maxHeight = (window.innerHeight * 0.65) + "px";
sampleSoupTextarea.readOnly = true;
sampleSoupTextarea.textContent = "Loading " + soupURL + ", please wait...";
// assemble elements.
document.getElementById("sampleSoupTable").appendChild(overlayDiv);
overlayDiv.appendChild(overlayInnerDiv);
overlayDiv.appendChild(overlayShadingDiv);
overlayInnerDiv.appendChild(introParagraph);
overlayInnerDiv.appendChild(soupSelectAll);
overlayInnerDiv.appendChild(sampleSoupTextarea);
// make sample soup overlay draggable with the mouse; text area is
// excluded (so the user can select the sample soup with the mouse).
dragDrop.initElement("sampleSoupOverlay", "sampleSoupTextArea");
// asynchronous request to retrieve the soup.
var sampleSoupRequest = new XMLHttpRequest();
// once the soup is loaded, put it into the textarea.
sampleSoupRequest.addEventListener("load", function() {
var sampleSoupTextarea = document.getElementById("sampleSoupTextArea");
if(sampleSoupTextarea)
sampleSoupTextarea.textContent = sampleSoupRequest.responseText;
});
// fire off request.
sampleSoupRequest.open("GET", soupURL);
sampleSoupRequest.send();
// success!
return true;
}
// colors used for the various symmetries. Color values are probably
// autogenerated from the symmetries' names, but I don't know how, so here's
// a hardcoded list.
var symmetryColors = new Object();
// standard symmetries.
symmetryColors["25pct"] = "#72da55";
symmetryColors["75pct"] = "#10963a";
symmetryColors["8x32" ] = "#6d0ecf";
symmetryColors["C1" ] = "black";
symmetryColors["C2_1" ] = "#f83e05";
symmetryColors["C2_2" ] = "#31a6d8";
symmetryColors["C2_4" ] = "#aceb02";
symmetryColors["C4_1" ] = "#d085ff";
symmetryColors["C4_4" ] = "#cd14a0";
symmetryColors["D2_+1"] = "#39bab9";
symmetryColors["D2_+2"] = "#747d16";
symmetryColors["D2_x" ] = "#fb71fe";
symmetryColors["D4_+1"] = "#f6b2b6";
symmetryColors["D4_+2"] = "#f8e612";
symmetryColors["D4_+4"] = "#cfc20e";
symmetryColors["D4_x1"] = "#ae360f";
symmetryColors["D4_x4"] = "#3e5b59";
symmetryColors["D8_1" ] = "#ed65b6";
symmetryColors["D8_4" ] = "#a621fb";
// "weird" symmetries
symmetryColors["D4 +4"] = "#d32f3f";
symmetryColors["D8_+4"] = "#0bb2a2";
// register an event handler so the soup overlay can be closed by pressing escape.
document.onkeydown = closeSoupOverlay;
`;
/*** INJECTED SCRIPT ENDS ***/
// inject a function to display sample soups in an overlay.
function injectScript(injectedScript) {
// create a new script element
var script = document.createElement("script");
script.type = "text/javascript";
// inject script text
script.textContent = injectedScript;
// append script to document
document.getElementsByTagName("head")[0].appendChild(script);
}
// sort the sample soups on a Catagolue object page by symmetry.
function handleSampleSoups(params) {
// regular expression to extract symmetries from sample soup links
var symRegex = /hashsoup\/(.*?)\/.*?\/(.*?)$/;
// hash of arrays containing sample soup links, grouped by symmetry
var soupLinks = new Object();
// total number of sample soups
var totalSoups = 0;
// paragraph holding the sample soups.
var sampleSoupsParagraph = findSampleSoupsParagraph();
// parse links on this page, and convert HTMLCollection to an array so it
// won't be "live" and change underneath us when we remove those links.
var links = Array.prototype.slice.call(sampleSoupsParagraph.getElementsByTagName("a"));
// we want to have soup links pop up an overlay with a textarea. In order
// to do this, we set an onclick handler on the links below that calls a
// function doing this. This function must live in the document, however,
// so we inject it now.
injectScript(sampleSoupOverlayScript);
// furthermore, we need to inject Paul Johnston's MD5 script, since
// Javascript lacks any built-in support for computing MD5 hashes.
injectScript(MD5Script);
// finally, we need to insert Peter-Paul Koch's element dragging script,
// so that the soup overlay can be dragged around the page with the mouse.
injectScript(dragAndDropSupportScript);
for(var i = 0; i < links.length; i++) {
var link = links[i];
var linkTarget = link.getAttribute("href");
var matches = symRegex.exec(linkTarget);
if(matches) {
// there's no autovivification, sigh.
if(!soupLinks[matches[1]]) {
soupLinks[matches[1]] = [];
}
totalSoups++;
soupLinks[matches[1]].push(link);
link.remove();
}
}
// now that all the links are collected and removed, add a table.
var table = document.createElement("table");
table.id = "sampleSoupTable";
table.style.backgroundColor = "#a0ddcc";
table.style.border = "2px solid";
table.style.borderRadius = "10px";
table.style.width = "100%";
// add table headers.
var headerRow = document.createElement("tr");
table.appendChild(headerRow);
var header1 = document.createElement("th");
header1.textContent = "Symmetry";
headerRow.appendChild(header1);
var header2 = document.createElement("th");
header2.innerHTML = "# Soups";
headerRow.appendChild(header2);
var header3 = document.createElement("th");
header3.textContent = "Sample soup links";
headerRow.appendChild(header3);
// insert table into page, replacing the old sample soup paragraph.
sampleSoupsParagraph.parentNode.replaceChild(table, sampleSoupsParagraph);
// iterate through symmetries and add new links.
var symmetries = Object.keys(soupLinks).sort();
for(var i = 0; i < symmetries.length; i++) {
var symmetry = symmetries[i];
var numSoups = soupLinks[symmetry].length;
// add a hr between table rows, for the sake of looks.
appendHR(table);
// create a new row holding the soup links for this symmetry.
var tableRow = document.createElement("tr");
table.appendChild(tableRow);
// create a table cell indicating the symmetry.
var tableCell1 = document.createElement("td");
tableRow.appendChild(tableCell1);
// create a link to the main census page for this rulesym.
var censusLink = document.createElement("a");
censusLink.href = "/census/" + params["rule"] + "/" + symmetry;
censusLink.textContent = symmetry;
tableCell1.appendChild(censusLink);
// create a table cell indicating the number of sample soup.
var tableCell2 = document.createElement("td");
tableCell2.textContent = numSoups;
tableRow.appendChild(tableCell2);
// create a table cell holding the sample soup links.
var tableCell3 = document.createElement("td");
for(var j = 0; j < numSoups; j++) {
var link = soupLinks[symmetry][j];
// modify link so that when the user's browsing with Javascript
// enabled, clicking it pops up an overlay with the sample soup
// in a textarea.
// NOTE: returning false here keeps the link's href from being
// loaded after the function has run. Note further that returning
// false FROM the function does not work.
link.setAttribute("onclick", 'return !overlaySoup("' + link.href + '", ' + (j + 1).toString() + ', ' + numSoups.toString() + ')');
// put link in this table cell.
tableCell3.appendChild(link);
tableCell3.appendChild(document.createTextNode(" "));
}
tableRow.appendChild(tableCell3);
}
// add another hr before the "totals" row.
appendHR(table);
// now add a row indicating the total number of sample soups.
var totalsRow = document.createElement("tr");
table.appendChild(totalsRow);
var totals1 = document.createElement("th");
totals1.textContent = "Total";
totalsRow.appendChild(totals1);
var totals2 = document.createElement("th");
totals2.innerHTML = totalSoups;
totalsRow.appendChild(totals2);
}
// add a textarea with the object in RLE format.
function objectToRLE(params) {
var prefix = params["prefix"];
var object = params["object"];
var rule = params["rule" ];
// regex to test prefix
var prefixRegex = /^x[pqs]/;
// only run for known objects.
if(object == null)
return;
// only run for spaceships (xq), oscillators (xp) and still lifes (xs).
if(!prefixRegex.test(prefix))
return;
// convert object to pattern, and pattern to RLE.
var pattern = apgcodeToPattern(object, rule);
var RLE = patternToRLE (pattern);
// find the "Comments" H2
var commentsH2 = findCommentsH2();
// create a new heading for the RLE code.
var RLEHeading = document.createElement("h3");
RLEHeading.textContent = "RLE";
// create "select all" link for RLE code.
var RLESelectAll = document.createElement("p");
var RLESelectAllLink = document.createElement("a");
RLESelectAll.style.marginTop = 0;
RLESelectAll.style.marginBottom = "0.5em";
RLESelectAll.style.fontFamily = "monospace";
RLESelectAllLink.href = "#";
RLESelectAllLink.textContent = "Select All";
RLESelectAllLink.setAttribute("onclick", 'document.getElementById("RLETextArea").select(); return false');
RLESelectAll.appendChild(RLESelectAllLink);
// create a textarea for the RLE code.
var RLETextArea = document.createElement("textarea");
RLETextArea.id = "RLETextArea";
RLETextArea.style.width = "100%";
RLETextArea.rows = "10";
RLETextArea.readOnly = true;
RLETextArea.textContent = RLE;
// insert the new nodes.
commentsH2.parentNode.insertBefore(RLEHeading, commentsH2);
commentsH2.parentNode.insertBefore(RLESelectAll, commentsH2);
commentsH2.parentNode.insertBefore(RLETextArea, commentsH2);
}
// add navigation
function addNavLinks(params) {
var rule = params["rule"];
var prefix = params["prefix"];
var symmetry = params["symmetry"];
// if symmetry is not set, default to C1.
if(!symmetry)
symmetry = "C1";
// heading containing the object's code
var titleHeading = findTitleHeading();
// main content div
var contentDiv = titleHeading.parentNode;
// new paragraph for navigation links
var navigationParagraph = document.createElement("p");
// insert navigation paragraph before title heading
contentDiv.insertBefore(navigationParagraph, titleHeading);
// add breadcrumb links to navigation paragraph
navigationParagraph.appendChild(document.createTextNode("You are here: "));
navigationParagraph.appendChild(makeLink("/census/", "Census"));
navigationParagraph.appendChild(document.createTextNode(breadcrumbSeparator));
navigationParagraph.appendChild(makeLink("/census/" + rule, rule));
navigationParagraph.appendChild(document.createTextNode(breadcrumbSeparator));
navigationParagraph.appendChild(makeLink("/census/" + rule + "/" + symmetry, symmetry));
navigationParagraph.appendChild(document.createTextNode(breadcrumbSeparator));
navigationParagraph.appendChild(makeLink("/census/" + rule + "/" + symmetry + "/" + prefix, prefix));
}
// ### MAIN ###
MAIN();
Side note for Opera users: all versions have been submitted to Opera Add-Ons, but none beyond 3.0 have been looked at yet.