Difference between revisions of "MediaWiki:Common.js"

Line 4: Line 4:
 
( function() {
 
( function() {
 
var $e = function (tag, text, classes, id) {
 
var $e = function (tag, text, classes, id) {
var d = document.createElement(tag);
+
var d = document.createElement(tag);
(classes !== undefined) && d.classList.add(...classes);
+
if (classes !== undefined) {
d.textContent = (text || "");
+
d.classList.add(...classes);
id && (d.id = id);
+
}
return d;
+
if(id) {
},
+
d.id = id;
// Function to streamline getElementById
+
}
$i = function (id, top) {
+
d.textContent = (text || "");
return (top === undefined ? document : top).getElementById(id);
+
return d;
},
+
};
// Function to streamline querySelector
+
// Function to streamline getElementById
$q = function (query, top) {
+
var $i = function (id, top) {
return (top === undefined ? document : top).querySelector(query);
+
if(top === undefined) {
},
+
top = document;
// Function to streamline querySelectorAll
+
}
$a = function (query, top) {
+
return top.getElementById(id);
return (top === undefined ? document : top).querySelectorAll(query);
+
};
},
+
// Function to streamline querySelector
// Function to look up a CSS variable (not needed)
+
var $q = function (query, top) {
/*$v = function (variable) {
+
if(top === undefined) {
return window.getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
+
top = document;
},*/
+
}
// Function to set CSS variables
+
return top.querySelector(query);
$sv = function (variable, value) {
+
};
document.documentElement.style.setProperty(variable, value);
+
// Function to streamline querySelectorAll
},
+
var $a = function (query, top) {
// Look for a Div that indicates we're a lexicon page
+
if(top === undefined) {
lexiconInfo = $i("lexiconInfo"),
+
top = document;
// Swappers have these two attributes.
+
}
swapperNexus = $a("input[data-swapper][data-swap-interval]");
+
return top.querySelectorAll(query);
 +
};
 +
// Function to look up a CSS variable (not needed)
 +
/*var $v = function (variable) {
 +
return window.getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
 +
};*/
 +
// Function to set CSS variables
 +
var $sv = function (variable, value) {
 +
document.documentElement.style.setProperty(variable, value);
 +
};
 +
// Look for a Div that indicates we're a lexicon page
 +
var lexiconInfo = $i("lexiconInfo");
 +
// Swappers have these two attributes.
 +
var swapperNexus = $a("input[data-swapper][data-swap-interval]");
  
  
Line 44: Line 57:
 
swapperNexus.forEach(function(nex) {
 
swapperNexus.forEach(function(nex) {
 
// Define variables inside here to hold stuff for the setInterval loop.
 
// Define variables inside here to hold stuff for the setInterval loop.
var overlord = $i("swap-override"), // A checkbox to pause ALL swapping on a page
+
var overlord = $i("swap-override"); // A checkbox to pause ALL swapping on a page
inverter = $i("swap-inverter"), // A checkbox to invert ALL animations (check to animate)
+
var inverter = $i("swap-inverter"); // A checkbox to invert ALL animations (check to animate)
ds = nex.dataset,
+
var ds = nex.dataset;
ident = ds.swapper,     // Name
+
var ident = ds.swapper;     // Name
rep = parseInt(ds.swapInterval); // Duration (in milliseconds)
+
var rep = parseInt(ds.swapInterval); // Duration (in milliseconds)
 
setInterval(function(){
 
setInterval(function(){
 
var x = nex.checked;
 
var x = nex.checked;
Line 54: Line 67:
 
$a("[data-swap-nexus=\"" + ident + "\"]").forEach(function(d) {
 
$a("[data-swap-nexus=\"" + ident + "\"]").forEach(function(d) {
 
// Rotate out the text!
 
// Rotate out the text!
var swap = d.dataset.swap.split(" "),
+
var swap = d.dataset.swap.split(" ");
newText = swap.shift();
+
var newText = swap.shift();
 
swap.push(newText);
 
swap.push(newText);
 
d.textContent = newText;
 
d.textContent = newText;
Line 69: Line 82:
 
// OVERRIDE LINK TITLES (in certain circumstances)
 
// OVERRIDE LINK TITLES (in certain circumstances)
 
//
 
//
$a("span[title][data-title-override] a[title]").forEach( x => x.removeAttribute("title") );
+
$a("span[title][data-title-override] a[title]").forEach( (x) => x.removeAttribute("title") );
  
  
Line 77: Line 90:
 
if(lexiconInfo !== null) {
 
if(lexiconInfo !== null) {
 
// Set variables
 
// Set variables
let lexicon = JSON.parse(lexiconInfo.textContent),
+
var lexicon = JSON.parse(lexiconInfo.textContent);
// Raw info, Array
+
// Raw info, Array
lexiconWords = lexicon.map(x => x[0]),
+
var lexiconWords = lexicon.map((x) => x[0]);
// Array of lexicon keys (derivation-sort-keys)
+
// Array of lexicon keys (derivation-sort-keys)
longestWord = Math.max(...lexicon.map(x => (x[1].word || x[0]).length)),
+
var longestWord = Math.max(...lexicon.map(function(x) {
// Length of the longest word in the lexicon
+
var z = (x[1].word || x[0]);
lexx = new Map(lexicon),
+
return z.length;
// Map with the lexicon info
+
}));
lexiMain = $i("lexicon"),
+
// Length of the longest word in the lexicon
// Div holding the lexicon words, etc, including padding.
+
var lexx = new Map(lexicon);
lexiDiv = $q(".lexi", lexiMain),
+
// Map with the lexicon info
// Div holding only the lexicon words, etc.
+
var lexiMain = $i("lexicon");
lexiDivDataset = lexiDiv.dataset,
+
// Div holding the lexicon words, etc, including padding.
// Simplifying an oft-used property
+
var lexiDiv = $q(".lexi", lexiMain);
sortMain, sortWord, sortCat, sortDef, checkPrevSorts, doSort,
+
// Div holding only the lexicon words, etc.
// Functions for sorting the lexicon
+
var lexiDivDataset = lexiDiv.dataset;
less = -1, more = 1, flagShowDerivs = true,
+
// Simplifying an oft-used property
// Used by the sorting functions
+
var sortMain;
prevSorts = new Set(),
+
var sortWord;
// Holds previously-used sorts
+
var sortCat;
Em, maxRowsPx, numMaxRows, allRowsPx,
+
var sortDef;
// Various variables used in determining the lexicon paddings' heights
+
var checkPrevSorts;
doRewrite, heartbeat,
+
var doSort;
// Functions for rewriting info to page depending on (sort and) scroll position
+
// Functions for sorting the lexicon
tooBig = (lexiconWords.length > 500),
+
var less = -1;
// Flag that notes if we should limit info displayed by scroll position
+
var more = 1;
undef = 0, uncr = 0, deriv = 0, uncat = 0, cats = new Map();
+
var flagShowDerivs = true;
// Used to fill out information in the lexicon infobox
+
// Used by the sorting functions
 +
var prevSorts = new Set();
 +
// Holds previously-used sorts
 +
var Em;
 +
var maxRowsPx;
 +
var numMaxRows;
 +
var allRowsPx;
 +
// Various variables used in determining the lexicon paddings' heights
 +
var doRewrite;
 +
var heartbeat;
 +
// Functions for rewriting info to page depending on (sort and) scroll position
 +
var tooBig = (lexiconWords.length > 500);
 +
// Flag that notes if we should limit info displayed by scroll position
 +
var undef = 0;
 +
var uncr = 0;
 +
var deriv = 0;
 +
var uncat = 0;
 +
var cats = new Map();
 +
// Used to fill out information in the lexicon infobox
 +
var tempo;
 +
var tempy;
  
 
// Set CSS variables for lexicon grids
 
// Set CSS variables for lexicon grids
$sv("--rows-num", "repeat(auto, " + (lexx.size + 2).toString() + ")");
+
tempo = lexx.size + 2;
$sv("--cols-num", (longestWord + 2).toString() + "em 5em calc(100% - " + (longestWord + 7).toString() + "em)");
+
$sv("--rows-num", "repeat(auto, " + tempo.toString() + ")");
$sv("--header-cols-num", (longestWord + 2).toString() + "em 5em calc(100% - " + (longestWord + 7).toString() + "em)");
+
tempo = longestWord + 2;
 +
tempy = tempo + 5;
 +
$sv("--cols-num", tempo.toString() + "em 5em calc(100% - " + tempy.toString() + "em)");
 +
$sv("--header-cols-num", tempo.toString() + "em 5em calc(100% - " + tempy.toString() + "em)");
  
 
// Create an element to determine how large a lexicon row will be, in pixels
 
// Create an element to determine how large a lexicon row will be, in pixels
Line 123: Line 159:
 
// Function to sort by word (and derivation)
 
// Function to sort by word (and derivation)
 
sortMain = function (a, b) {
 
sortMain = function (a, b) {
var x = lexx.get(a).sort, y = lexx.get(b).sort;
+
var x = lexx.get(a).sort;
 +
var y = lexx.get(b).sort;
 
if(x < y) {
 
if(x < y) {
 
return less;
 
return less;
} else if (x > y) {
+
}
 +
if (x > y) {
 
return more;
 
return more;
 
}
 
}
return prevSorts.size ? checkPrevSorts(a, b) : 0;
+
if(prevSorts.size) {
 +
return checkPrevSorts(a, b);
 +
}
 +
return 0;
 
};
 
};
 
// Function to sort by word, ignoring if its a derivation or not
 
// Function to sort by word, ignoring if its a derivation or not
 
sortWord = function (a, b) {
 
sortWord = function (a, b) {
var x = lexx.get(a), y = lexx.get(b);
+
var x = lexx.get(a);
 +
var y = lexx.get(b);
 
x = x.selfSort || x.sort;
 
x = x.selfSort || x.sort;
 
y = y.selfSort || y.sort;
 
y = y.selfSort || y.sort;
 
if(x < y) {
 
if(x < y) {
 
return less;
 
return less;
} else if (x > y) {
+
}
 +
if (x > y) {
 
return more;
 
return more;
 
}
 
}
return prevSorts.size ? checkPrevSorts(a, b) : 0;
+
if(prevSorts.size) {
 +
return checkPrevSorts(a, b);
 +
}
 +
return 0;
 
};
 
};
 
// Function to sort by category
 
// Function to sort by category
 
sortCat = function (a, b) {
 
sortCat = function (a, b) {
var x = lexx.get(a).cat, y = lexx.get(b).cat;
+
var x = lexx.get(a).cat;
 +
var y = lexx.get(b).cat;
 
if(x < y) {
 
if(x < y) {
 
return less;
 
return less;
} else if (x > y) {
+
}
 +
if (x > y) {
 
return more;
 
return more;
 
}
 
}
return prevSorts.size ? checkPrevSorts(a, b) : 0;
+
if(prevSorts.size) {
 +
return checkPrevSorts(a, b);
 +
}
 +
return 0;
 
};
 
};
 
// Function to sort by definition
 
// Function to sort by definition
 
sortDef = function (a, b) {
 
sortDef = function (a, b) {
var x = lexx.get(a), y = lexx.get(b);
+
var x = lexx.get(a);
 +
var y = lexx.get(b);
 
// Change these to uppercase to eliminate case-based sorting errors
 
// Change these to uppercase to eliminate case-based sorting errors
x = (x.defSort || x.def).toUpperCase();
+
x = (x.defSort || x.def);
y = (y.defSort || y.def).toUpperCase();
+
x = x.toUpperCase();
 +
y = (y.defSort || y.def);
 +
y = y.toUpperCase();
 
if(x < y) {
 
if(x < y) {
 
return less;
 
return less;
} else if (x > y) {
+
}
 +
if (x > y) {
 
return more;
 
return more;
 
}
 
}
return prevSorts.size ? checkPrevSorts(a, b) : 0;
+
if(prevSorts.size) {
 +
return checkPrevSorts(a, b);
 +
}
 +
return 0;
 
};
 
};
 
// When search terms are equal, use previous sort criteria (if any).
 
// When search terms are equal, use previous sort criteria (if any).
 
checkPrevSorts = function(a, b) {
 
checkPrevSorts = function(a, b) {
var val = 0,
+
var val = 0;
prev = prevSorts, // save prevSorts
+
var test;
saved = [...prevSorts.values()]; // put all previous sorts in an array format
+
var rv;
 +
// save prevSorts
 +
var prev = prevSorts;
 +
// put all previous sorts in an array format
 +
var saved = [...prevSorts.values()];
 
// Clear prevSorts so we don't loop infinitely.
 
// Clear prevSorts so we don't loop infinitely.
 
prevSorts = new Set();
 
prevSorts = new Set();
 
// Go through previous sorts in a last-in first-out method.
 
// Go through previous sorts in a last-in first-out method.
 
while(saved.length > 0) {
 
while(saved.length > 0) {
let test = saved.pop(), rv;
+
test = saved.pop();
 
// Test the sort
 
// Test the sort
 
rv = test(a, b);
 
rv = test(a, b);
Line 189: Line 251:
 
prevSorts = prev;
 
prevSorts = prev;
 
return val;
 
return val;
}
+
};
  
 
// Function that rewrites the page to match the newly sorted content
 
// Function that rewrites the page to match the newly sorted content
 
doRewrite = function(count) {
 
doRewrite = function(count) {
 
// Variables used in this function
 
// Variables used in this function
var cols = 0, id, info, special;
+
var cols = 0;
 +
var id;
 +
var info;
 +
var special;
 
// Make sure we got a number to work with
 
// Make sure we got a number to work with
(count === undefined) && (count = Math.max(0, Math.round(Number(lexiDivDataset.top) / Em)));
+
if (count === undefined) {
 +
count = Math.max(0, Math.round(Number(lexiDivDataset.top) / Em));
 +
}
 
// Go through each Div one at a time
 
// Go through each Div one at a time
 
$a("div", lexiDiv).forEach(function(node) {
 
$a("div", lexiDiv).forEach(function(node) {
var txt, cL = node.classList;
+
var txt;
 +
var cL = node.classList;
 
// There are three columns
 
// There are three columns
 
switch(cols) {
 
switch(cols) {
Line 226: Line 294:
 
// Reset column to 0
 
// Reset column to 0
 
cols = 0;
 
cols = 0;
 +
break;
 
}
 
}
 
// Set the text
 
// Set the text
 
node.textContent = txt;
 
node.textContent = txt;
 
// Remove any classes that may exist, aside from the first (word, cat or def)
 
// Remove any classes that may exist, aside from the first (word, cat or def)
[...cL].slice(1).forEach(c => cL.remove(c));
+
node = [...cL];
 +
node.slice(1).forEach((c) => cL.remove(c));
 
// If special is set, add it as a class
 
// If special is set, add it as a class
 
if(special) {
 
if(special) {
Line 242: Line 312:
 
// Go through each word and add a new property .sort
 
// Go through each word and add a new property .sort
 
lexiconWords.forEach(function(id) {
 
lexiconWords.forEach(function(id) {
var info = lexx.get(id),
+
var info = lexx.get(id);
w = (info.word || id),
+
// The spreadsheet works best when Zs are used to shunt something to the end of the line
// The spreadsheet works best when Zs are used to shunt something to the end of the line
+
// In javascript, Zs actuallly move you above all lowercase letters
// In javascript, Zs actuallly move you above all lowercase letters
+
// So, we change Zs to ~s to get the desired effect
// So, we change Zs to ~s to get the desired effect
+
var sort = id.replace(/Z/g, "~");
sort = id.replace(/Z/g, "~");
 
 
// Create the sort property
 
// Create the sort property
 
info.sort = sort;
 
info.sort = sort;
Line 260: Line 329:
 
// Function that is called by the sort button
 
// Function that is called by the sort button
 
doSort = function(doNotRewrite) {
 
doSort = function(doNotRewrite) {
var dir = $q("select[name=\"dir\"]").value,
+
var dir = $q("select[name=\"dir\"]").value;
what = $q("select[name=\"sort\"]").value,
+
var what = $q("select[name=\"sort\"]").value;
//table = $q(".lexi", $i("lexicon")),
+
var lexiMainClasses = lexiMain.classList;
//tcl = table.classList,
+
var sortFunction;
//template = ['"wordHead catHead defHead"'],
 
lexiMainClasses = lexiMain.classList,
 
sortFunction;
 
 
// Change the value of "less" and "more" and change classes on the lexicon to indicate this
 
// Change the value of "less" and "more" and change classes on the lexicon to indicate this
 
if(dir === "asc") {
 
if(dir === "asc") {
Line 298: Line 364:
 
break;
 
break;
 
default:
 
default:
console.log("A strange error occurred while trying to sort: [" + what + "]");
+
window.console.log("A strange error occurred while trying to sort: [" + what + "]");
 
return;
 
return;
 
}
 
}
Line 308: Line 374:
 
prevSorts.add(sortFunction);
 
prevSorts.add(sortFunction);
 
// Trigger rewrite
 
// Trigger rewrite
(doNotRewrite === true) || doRewrite();
+
if (doNotRewrite === true) {
 +
doRewrite();
 +
}
 
};
 
};
 
// Add the above to the sort button
 
// Add the above to the sort button
$i("dosort").addEventListener("click", function (e) { doSort(false) });
+
$i("dosort").addEventListener("click", function (e) { doSort(false); });
 
// Sort right away.
 
// Sort right away.
 
doSort(true);
 
doSort(true);
Line 324: Line 392:
 
lexiconWords.slice(0, numMaxRows).forEach(function(id) {
 
lexiconWords.slice(0, numMaxRows).forEach(function(id) {
 
// Get all the info we need
 
// Get all the info we need
var info = lexx.get(id),
+
var info = lexx.get(id);
w = info.word || id,
+
var w = info.word || id;
sort = info.sort,
+
var extra = (
extra = info.special ? [info.special] : [];
+
info.special ? [info.special] : []
//console.log([w, id, sort]);
+
);
 +
//window.console.log([w, id, sort]);
 
// Add the word to the page
 
// Add the word to the page
 
lexiDiv.append(
 
lexiDiv.append(
Line 341: Line 410:
 
$sv("--flex-top", "0px");
 
$sv("--flex-top", "0px");
 
// Determine how muxh empty space lies beneath the printed rows and set the CSS variable accordingly
 
// Determine how muxh empty space lies beneath the printed rows and set the CSS variable accordingly
$sv("--flex-bottom", (allRowsPx - maxRowsPx).toString() + "px");
+
tooBig = allRowsPx - maxRowsPx;
 +
$sv("--flex-bottom", tooBig.toString() + "px");
  
 
// Create a function to periodically check the scroll state
 
// Create a function to periodically check the scroll state
 
heartbeat = function() {
 
heartbeat = function() {
var changed = lexiDivDataset.changed,
+
var changed = lexiDivDataset.changed;
scrollTop, clientHeightTwice,
+
var scrollTop;
top, bottom;
+
var clientHeightTwice;
 +
var top;
 +
var bottom;
 
if(changed !== "true") {
 
if(changed !== "true") {
 
// Nothing has changed.
 
// Nothing has changed.
Line 382: Line 454:
 
lexiDivDataset.top = top.toString();
 
lexiDivDataset.top = top.toString();
 
lexiDivDataset.scrollTop = scrollTop.toString();
 
lexiDivDataset.scrollTop = scrollTop.toString();
}
+
};
 
// Start the heartbeat
 
// Start the heartbeat
 
lexiDivDataset.changed = "false";
 
lexiDivDataset.changed = "false";
Line 408: Line 480:
 
// No special padding/row-adjustments necessary, just print the entire lexicon
 
// No special padding/row-adjustments necessary, just print the entire lexicon
 
lexx.forEach(function(info, id) {
 
lexx.forEach(function(info, id) {
var w = info.word || id,
+
var w = (info.word || id);
sort = info.sort,
+
var extra = (
extra = info.special ? [info.special] : [];
+
info.special ? [info.special] : []
//console.log([w, id, sort]);
+
);
 +
//window.console.log([w, id, sort]);
 
lexiDiv.append(
 
lexiDiv.append(
 
$e("div", w, ["word", ...extra]),
 
$e("div", w, ["word", ...extra]),
Line 429: Line 502:
 
$q("span.lex_box_all").textContent = lexicon.length.toString();
 
$q("span.lex_box_all").textContent = lexicon.length.toString();
 
lexx.forEach(function(info) {
 
lexx.forEach(function(info) {
var cat = info.cat || "uncat",
+
var cat = (info.cat || "uncat");
ncat = cats.get(cat) || 0,
+
var ncat = (cats.get(cat) || 0);
def = info.def,
+
var def = info.def;
word = info.word,
+
var word = info.word;
dv = info.special === "deriv";
+
var dv = (info.special === "deriv");
 
// Check definitions
 
// Check definitions
 
if(!def) {
 
if(!def) {

Revision as of 18:58, 27 November 2019

/* Any JavaScript here will be loaded for all users on every page load. */

// Function to streamline element creation
( function() {
var $e = function (tag, text, classes, id) {
	var d = document.createElement(tag);
	if (classes !== undefined) {
		d.classList.add(...classes);
	}
	if(id) {
		d.id = id;
	}
	d.textContent = (text || "");
	return d;
};
// Function to streamline getElementById
var $i = function (id, top) {
	if(top === undefined) {
		top = document;
	}
	return top.getElementById(id);
};
// Function to streamline querySelector
var $q = function (query, top) {
	if(top === undefined) {
		top = document;
	}
	return top.querySelector(query);
};
// Function to streamline querySelectorAll
var $a = function (query, top) {
	if(top === undefined) {
		top = document;
	}
	return top.querySelectorAll(query);
};
// Function to look up a CSS variable (not needed)
/*var $v = function (variable) {
	return window.getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
};*/
// Function to set CSS variables
var $sv = function (variable, value) {
	document.documentElement.style.setProperty(variable, value);
};
// Look for a Div that indicates we're a lexicon page
var lexiconInfo = $i("lexiconInfo");
// Swappers have these two attributes.
var swapperNexus = $a("input[data-swapper][data-swap-interval]");



//
// TEXT SWAP-OUT ANIMATION
//
if(swapperNexus.length) {
	// Check each swapper separately.
	swapperNexus.forEach(function(nex) {
		// Define variables inside here to hold stuff for the setInterval loop.
		var overlord = $i("swap-override");  // A checkbox to pause ALL swapping on a page
		var inverter = $i("swap-inverter");  // A checkbox to invert ALL animations (check to animate)
		var ds = nex.dataset;
		var ident = ds.swapper;     // Name
		var rep = parseInt(ds.swapInterval); // Duration (in milliseconds)
		setInterval(function(){
			var x = nex.checked;
			if(!overlord.checked &&	(inverter.checked ? x : !x) ) { // Pause when checked
				$a("[data-swap-nexus=\"" + ident + "\"]").forEach(function(d) {
					// Rotate out the text!
					var swap = d.dataset.swap.split(" ");
					var newText = swap.shift();
					swap.push(newText);
					d.textContent = newText;
					d.dataset.swap = swap.join(" ");
				});
			}
		}, rep);
	});
}


//
// OVERRIDE LINK TITLES (in certain circumstances)
//
$a("span[title][data-title-override] a[title]").forEach( (x) => x.removeAttribute("title") );


//
// HANDLE LEXICONS
//
if(lexiconInfo !== null) {
	// Set variables
	var lexicon = JSON.parse(lexiconInfo.textContent);
	// Raw info, Array
	var lexiconWords = lexicon.map((x) => x[0]);
	// Array of lexicon keys (derivation-sort-keys)
	var longestWord = Math.max(...lexicon.map(function(x) {
		var z = (x[1].word || x[0]);
		return z.length;
	}));
	// Length of the longest word in the lexicon
	var lexx = new Map(lexicon);
	// Map with the lexicon info
	var lexiMain = $i("lexicon");
	// Div holding the lexicon words, etc, including padding.
	var lexiDiv = $q(".lexi", lexiMain);
	// Div holding only the lexicon words, etc.
	var lexiDivDataset = lexiDiv.dataset;
	// Simplifying an oft-used property
	var sortMain;
	var sortWord;
	var sortCat;
	var sortDef;
	var checkPrevSorts;
	var doSort;
	// Functions for sorting the lexicon
	var less = -1;
	var more = 1;
	var flagShowDerivs = true;
	// Used by the sorting functions
	var prevSorts = new Set();
	// Holds previously-used sorts
	var Em;
	var maxRowsPx;
	var numMaxRows;
	var allRowsPx;
	// Various variables used in determining the lexicon paddings' heights
	var doRewrite;
	var heartbeat;
	// Functions for rewriting info to page depending on (sort and) scroll position
	var tooBig = (lexiconWords.length > 500);
	// Flag that notes if we should limit info displayed by scroll position
	var undef = 0;
	var uncr = 0;
	var deriv = 0;
	var uncat = 0;
	var cats = new Map();
	// Used to fill out information in the lexicon infobox
	var tempo;
	var tempy;

	// Set CSS variables for lexicon grids
	tempo = lexx.size + 2;
	$sv("--rows-num", "repeat(auto, " + tempo.toString() + ")");
	tempo = longestWord + 2;
	tempy = tempo + 5;
	$sv("--cols-num", tempo.toString() + "em 5em calc(100% - " + tempy.toString() + "em)");
	$sv("--header-cols-num", tempo.toString() + "em 5em calc(100% - " + tempy.toString() + "em)");

	// Create an element to determine how large a lexicon row will be, in pixels
	document.body.append($e("div", "", [], "hiddenEm"));
	Em = $i("hiddenEm").clientHeight * 2.5; // Row-height is 2.5em.
	// Determine the maximum number of rows to keep loaded
	numMaxRows = Math.max(Math.floor(window.innerHeight / Em) * 5, 100);
	// Determine the height of the filled rows
	maxRowsPx = numMaxRows * Em;
	// Determine the height that would be generated IF all rows were shown at once
	allRowsPx = lexiconWords.length * Em;

	// Function to sort by word (and derivation)
	sortMain = function (a, b) {
		var x = lexx.get(a).sort;
		var y = lexx.get(b).sort;
		if(x < y) {
			return less;
		}
		if (x > y) {
			return more;
		}
		if(prevSorts.size) {
			return checkPrevSorts(a, b);
		}
		return 0;
	};
	// Function to sort by word, ignoring if its a derivation or not
	sortWord = function (a, b) {
		var x = lexx.get(a);
		var y = lexx.get(b);
		x = x.selfSort || x.sort;
		y = y.selfSort || y.sort;
		if(x < y) {
			return less;
		}
		if (x > y) {
			return more;
		}
		if(prevSorts.size) {
			return checkPrevSorts(a, b);
		}
		return 0;
	};
	// Function to sort by category
	sortCat = function (a, b) {
		var x = lexx.get(a).cat;
		var y = lexx.get(b).cat;
		if(x < y) {
			return less;
		}
		if (x > y) {
			return more;
		}
		if(prevSorts.size) {
			return checkPrevSorts(a, b);
		}
		return 0;
	};
	// Function to sort by definition
	sortDef = function (a, b) {
		var x = lexx.get(a);
		var y = lexx.get(b);
		// Change these to uppercase to eliminate case-based sorting errors
		x = (x.defSort || x.def);
		x = x.toUpperCase();
		y = (y.defSort || y.def);
		y = y.toUpperCase();
		if(x < y) {
			return less;
		}
		if (x > y) {
			return more;
		}
		if(prevSorts.size) {
			return checkPrevSorts(a, b);
		}
		return 0;
	};
	// When search terms are equal, use previous sort criteria (if any).
	checkPrevSorts = function(a, b) {
		var val = 0;
		var test;
		var rv;
		// save prevSorts
		var prev = prevSorts;
		// put all previous sorts in an array format
		var saved = [...prevSorts.values()];
		// Clear prevSorts so we don't loop infinitely.
		prevSorts = new Set();
		// Go through previous sorts in a last-in first-out method.
		while(saved.length > 0) {
			test = saved.pop();
			// Test the sort
			rv = test(a, b);
			// Check if we're unequal
			if(rv) {
			// Clear saved (to end the loop)
				saved = [];
			// Save the sort value
				val = rv;
			}
		}
		// Restore prevSorts
		prevSorts = prev;
		return val;
	};

	// Function that rewrites the page to match the newly sorted content
	doRewrite = function(count) {
		// Variables used in this function
		var cols = 0;
		var id;
		var info;
		var special;
		// Make sure we got a number to work with
		if (count === undefined) {
			count = Math.max(0, Math.round(Number(lexiDivDataset.top) / Em));
		}
		// Go through each Div one at a time
		$a("div", lexiDiv).forEach(function(node) {
			var txt;
			var cL = node.classList;
			// There are three columns
			switch(cols) {
				// Column 1: the word itself
				case 0:
					// Set up the variables so we don't need to check again for the next two columns
					id = lexiconWords[count];
					info = lexx.get(id);
					special = info.special;
					// Word may be in info.word or it may be the sorting id.
					txt = (info.word || id);
					// Advance to next column next time
					cols++;
					break;
				case 1:
					// Category is straightforward
					txt = info.cat;
					// Advance to next column next time
					cols++;
					break;
				case 2:
					// Category is straightforward
					txt = info.def;
					// Advance to next row next time
					count++;
					// Reset column to 0
					cols = 0;
					break;
			}
			// Set the text
			node.textContent = txt;
			// Remove any classes that may exist, aside from the first (word, cat or def)
			node = [...cL];
			node.slice(1).forEach((c) => cL.remove(c));
			// If special is set, add it as a class
			if(special) {
				cL.add(special);
			}
		});
		// Mark on the main Div if we're sorting by derivations
		lexiMain.classList.toggle("derivationsMarked", flagShowDerivs);
	};

	// Go through each word and add a new property .sort
	lexiconWords.forEach(function(id) {
		var info = lexx.get(id);
		// The spreadsheet works best when Zs are used to shunt something to the end of the line
		// In javascript, Zs actuallly move you above all lowercase letters
		// So, we change Zs to ~s to get the desired effect
		var sort = id.replace(/Z/g, "~");
		// Create the sort property
		info.sort = sort;
		// Do the same process for the .selfSort property (used by derviations)
		if(info.selfSort) {
			info.selfSort = info.selfSort.replace(/Z/g, "~");
		}
		// Save the info
		lexx.set(id, info);
	});

	// Function that is called by the sort button
	doSort = function(doNotRewrite) {
		var dir = $q("select[name=\"dir\"]").value;
		var what = $q("select[name=\"sort\"]").value;
		var lexiMainClasses = lexiMain.classList;
		var sortFunction;
		// Change the value of "less" and "more" and change classes on the lexicon to indicate this
		if(dir === "asc") {
			less = -1;
			more = 1;
			lexiMainClasses.add("asc");
			lexiMainClasses.remove("desc");
		} else {
			less = 1;
			more = -1;
			lexiMainClasses.add("desc");
			lexiMainClasses.remove("asc");
		}
		// "Zero out" this flag
		flagShowDerivs = false;
		// Set sortFunction to the correct sorting function
		switch(what) {
			case "word":
				sortFunction = sortMain;
				// Re-enable the flag because we use it here.
				flagShowDerivs = true;
				break;
			case "noDeriv":
				sortFunction = sortWord;
				break;
			case "cat":
				sortFunction = sortCat;
				break;
			case "def":
				sortFunction = sortDef;
				break;
			default:
				window.console.log("A strange error occurred while trying to sort: [" + what + "]");
				return;
		}
		// Remove the sort from previous sorts (if it's there)
		prevSorts.delete(sortFunction);
		// Sort words via the chosen function
		lexiconWords.sort(sortFunction);
		// Add the sort to previous sorts
		prevSorts.add(sortFunction);
		// Trigger rewrite
		if (doNotRewrite === true) {
			doRewrite();
		}
	};
	// Add the above to the sort button
	$i("dosort").addEventListener("click", function (e) { doSort(false); });
	// Sort right away.
	doSort(true);

	// Check to see if we need to limit the number of rows onscreen
	if(tooBig) {
		// Yes, we need to limit
		// Save initial position as "at the top"
		lexiDivDataset.top = "0";
		lexiDivDataset.scrollTop = "0";
		// Go through the words up to the predetermined limit
		lexiconWords.slice(0, numMaxRows).forEach(function(id) {
			// Get all the info we need
			var info = lexx.get(id);
			var w = info.word || id;
			var extra = (
				info.special ? [info.special] : []
			);
			//window.console.log([w, id, sort]);
			// Add the word to the page
			lexiDiv.append(
				$e("div", w, ["word", ...extra]),
				$e("div", info.cat, ["cat", ...extra]),
				$e("div", info.def, ["def", ...extra])
			);
		});
		// Note if we have derivations
		lexiMain.classList.toggle("derivationsMarked", flagShowDerivs);
		// Set CSS variables to have no padding on top
		$sv("--flex-top", "0px");
		// Determine how muxh empty space lies beneath the printed rows and set the CSS variable accordingly
		tooBig = allRowsPx - maxRowsPx;
		$sv("--flex-bottom", tooBig.toString() + "px");

		// Create a function to periodically check the scroll state
		heartbeat = function() {
			var changed = lexiDivDataset.changed;
			var scrollTop;
			var clientHeightTwice;
			var top;
			var bottom;
			if(changed !== "true") {
				// Nothing has changed.
				if(changed === "setting") {
					lexiDivDataset.changed = "false";
				}
				return;
			}
			// Where are we scrolled to?
			scrollTop = lexiMain.scrollTop;
			// How far up should the padding end?
			clientHeightTwice = lexiMain.clientHeight * 2;
			// What is the current amount of top-padding?
			top = parseInt(lexiDivDataset.top);
			// Determine if we're scrolled within range of an edge
			if(scrollTop >= top && scrollTop + clientHeightTwice - maxRowsPx <= top) {
				// No need to change padding and words
				return;
			}
			// Note that we're changing stuff.
			lexiDivDataset.changed = "setting";
			// Determine new padding for top
			top = Math.min(allRowsPx - maxRowsPx, Math.max(0, scrollTop - clientHeightTwice));
			// Clip to a row boundary
			top -= top % Em;
			// New padding for bottom, too
			bottom = Math.max(0, allRowsPx - (top + maxRowsPx));
			// Rewrite rows starting at the proper position
			doRewrite(Math.max(0, Math.round(top / Em)));
			// Set the top and bottom paddings by CSS variables
			$sv("--flex-top", top.toString() + "px");
			$sv("--flex-bottom", bottom.toString() + "px");
			// Save the top padding and the current position
			lexiDivDataset.top = top.toString();
			lexiDivDataset.scrollTop = scrollTop.toString();
		};
		// Start the heartbeat
		lexiDivDataset.changed = "false";
		setInterval(heartbeat, 300);
		// Start the listener for the scroll event
		lexiMain.addEventListener("scroll", function(event) {
			// When the visible rows are moved down by changing 
		 	// the height of the optimistic row above them, the browser automatically
		 	// scrolls them back into view, which in turn creates another render
		 	// becuase the onScroll is called, resulting in an infinite loop.
		 	// To solve this, we get the snapshot before the DOM is updated
		 	// and check for a mismatch between the scrollTop before and after. 
		 	// If such a mismatch exists, it means that the scroll 
		 	// was done by the browser, and not the user, and therefore
		 	// we apply the scrollTop from the snapshot.
		 	if (lexiDivDataset.changed !== "setting") {
				lexiDivDataset.changed = "true";
			} else {
				lexiDivDataset.changed = "false";
				// Reset scrollTop
				lexiMain.scrollTop = Number(lexiDivDataset.scrollTop);
			}
		});
	} else {
		// No special padding/row-adjustments necessary, just print the entire lexicon
		lexx.forEach(function(info, id) {
			var w = (info.word || id);
			var extra = (
				info.special ? [info.special] : []
			);
			//window.console.log([w, id, sort]);
			lexiDiv.append(
				$e("div", w, ["word", ...extra]),
				$e("div", info.cat, ["cat", ...extra]),
				$e("div", info.def, ["def", ...extra])
			);
		});
		// Note if we have derivations
		lexiMain.classList.toggle("derivationsMarked", flagShowDerivs);
	}

	//
	// Lexicon Infobox
	//
	if($q("span.lex_box_all") !== null) {
		// total number of words
		$q("span.lex_box_all").textContent = lexicon.length.toString();
		lexx.forEach(function(info) {
			var cat = (info.cat || "uncat");
			var ncat = (cats.get(cat) || 0);
			var def = info.def;
			var word = info.word;
			var dv = (info.special === "deriv");
			// Check definitions
			if(!def) {
				undef++;
			} else if (!word) {
				uncr++;
			}
			// Check categories
			if(cat === "uncat") {
				uncat++;
			} else {
				if(cat === "#") {
					cat = "number";
				}
				ncat++;
				cats.set(cat, ncat);
				if(!word) {
					uncr++;
				}
			}
			// Check derivations
			if(dv) {
				deriv++;
			}
		});
		// undefined words
		$q("span.lex_box_undef").textContent = undef.toString();
		// uncreated words (definition and/or category, though)
		$q("span.lex_box_uncr").textContent = uncr.toString();
		// derivations
		$q("span.lex_box_deriv").textContent = deriv.toString();
		// uncategorized words
		$q("span.lex_box_uncat").textContent = uncat.toString();
		// words in categories
		cats.forEach(function(n, cat) {
			$q("span.lex_box_cat_" + cat).textContent = n.toString();
		});
	}

}
}() );