Difference between revisions of "MediaWiki:Common.js"
Line 397: | Line 397: | ||
$e("div", w, extraW), | $e("div", w, extraW), | ||
$e("div", info.cat, extraC), | $e("div", info.cat, extraC), | ||
− | $e("div", info.def, extraD) | + | $e("div", info.def, extraD) |
); | ); | ||
}); | }); | ||
Line 491: | Line 491: | ||
$e("div", w, extraW), | $e("div", w, extraW), | ||
$e("div", info.cat, extraC), | $e("div", info.cat, extraC), | ||
− | $e("div", info.def, extraD) | + | $e("div", info.def, extraD) |
); | ); | ||
}); | }); |
Revision as of 06:57, 28 November 2019
/* Any JavaScript here will be loaded for all users on every page load. */ // Function to streamline element creation var $e = function (tag, text, classes, id) { var d = document.createElement(tag), cl = d.classList; if (classes !== undefined) { classes.forEach( function(c) { cl.add(c); }); } if(id) { d.id = id; } d.textContent = (text || ""); return d; }, // Function to streamline getElementById $i = function (id, top) { if(top === undefined) { top = document; } return top.getElementById(id); }, // Function to streamline querySelector $q = function (query, top) { if(top === undefined) { top = document; } return top.querySelector(query); }, // Function to streamline querySelectorAll $a = function (query, top) { if(top === undefined) { top = document; } return top.querySelectorAll(query); }, // Function to look up a CSS variable (not needed) /*$v = function (variable) { return window.getComputedStyle(document.documentElement).getPropertyValue(variable).trim(); },*/ // Function to set CSS variables $sv = function (variable, value) { document.documentElement.style.setProperty(variable, value); }, // Look for a Div that indicates we're a lexicon page lexiconInfo = $i("lexiconInfo"), // Swappers have these two attributes. 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 inverter = $i("swap-inverter"), // A checkbox to invert ALL animations (check to animate) ds = nex.dataset, ident = ds.swapper, // Name 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(" "), 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( function(x) { x.removeAttribute("title"); }); // // HANDLE LEXICONS // if(lexiconInfo !== null) { // Set variables var lexicon = JSON.parse(lexiconInfo.textContent), // Raw info, Array lexiconWords = lexicon.map(function(x) { return x[0]; }), // Array of lexicon keys (derivation-sort-keys) longestWord = lexicon.map(function(x) { var z = (x[1].word || x[0]); return z.length; }).reduce(function(acc, val) { return acc > val ? acc : val; }), // Length of the longest word in the lexicon lexx = new Map(lexicon), // Map with the lexicon info lexiMain = $i("lexicon"), // Div holding the lexicon words, etc, including padding. lexiDiv = $q(".lexi", lexiMain), // Div holding only the lexicon words, etc. lexiDivDataset = lexiDiv.dataset, // Simplifying an oft-used property sortMain, sortWord, sortCat, sortDef, checkPrevSorts, doSort, // Functions for sorting the lexicon less = -1, more = 1, flagShowDerivs = true, // Used by the sorting functions prevSorts = new Set(), // Holds previously-used sorts Em, maxRowsPx, numMaxRows, allRowsPx, // Various variables used in determining the lexicon paddings' heights doRewrite, heartbeat, // Functions for rewriting info to page depending on (sort and) scroll position tooBig = (lexiconWords.length > 500), // Flag that notes if we should limit info displayed by scroll position undef = 0, uncr = 0, deriv = 0, uncat = 0, cats = new Map(), // Used to fill out information in the lexicon infobox tempo, tempy; // Temp variables // 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, 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), 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, 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), 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, test, rv, // save prevSorts prev = prevSorts, // put all previous sorts in an array format saved = Array.from(prevSorts); // 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, id, info, 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, 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) Array.from(cL).slice(1).forEach(function(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 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, what = $q("select[name=\"sort\"]").value, lexiMainClasses = lexiMain.classList, 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), w = info.word || id, extraW = ["word"], extraC = ["cat"], extraD = ["def"], special = info.special; if (special) { extraW.unshift(special); extraC.unshift(special); extraD.unshift(special); } //window.console.log([w, id, sort]); // Add the word to the page lexiDiv.append( $e("div", w, extraW), $e("div", info.cat, extraC), $e("div", info.def, extraD) ); }); // 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) { // Get all the info we need var w = info.word || id, extraW = ["word"], extraC = ["cat"], extraD = ["def"], special = info.special; if (special) { extraW.unshift(special); extraC.unshift(special); extraD.unshift(special); } //window.console.log([w, id, sort]); // Add the word to the page lexiDiv.append( $e("div", w, extraW), $e("div", info.cat, extraC), $e("div", info.def, extraD) ); }); // 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(); }); } }