|
'use strict'; |
|
|
|
// This let statement selects elements from the HTML document using a CSS selector. |
|
// Specifically, we're making an Array of all table rows (each news listing is |
|
// a table row in an html table). The css selector here says, find the 'tr' elements |
|
// that are nested at any depth below a tbody which is nested at any depth below any |
|
// element bearing the class 'itemlist' -- the dot in front of itemlist indicates |
|
// we're talking about a CSS class, not an element. |
|
// Look up css selectors and the document.querySelectorAll |
|
// function for more about that. |
|
// When you do a querySelectorAll, you get a NodeList in return, not quite the same |
|
// thing as an array, because you can't slice it. Well, maybe you could using css selectors |
|
// but lets not get into that. The 'Array.from' here converts our nodelist into and array |
|
// we can slice on the very next line. |
|
let elements = Array.from(document.querySelectorAll(".itemlist tbody tr")); |
|
|
|
// Since we'll be taking each news entry as a group of 3 tr's, we want to take the slice of |
|
// the tr's that is divisible by 3, the remaining 0-2 elements we'll call 'leftovers' |
|
// and append them back to the table after after everything else is done. |
|
let leftoverCount = elements.length - elements.length % 3; |
|
|
|
// Here, we're making a new iterator of the 'elements' array from the line above. |
|
// entries() is a built-in function that iterates over each element in the array and |
|
// pairs it with it's numeric index. So ['a', 'b'] becomes [[0, 'a'], [1, 'b']]. |
|
let ielements = elements.slice(0, leftoverCount).entries(); |
|
|
|
// Here we save the leftovers to be appended at the end. |
|
let leftovers = elements.slice(leftoverCount); |
|
|
|
// Just initializing some variables that will be used in the upcoming lines. |
|
// 'i' will be the index variable and 'e' will be the HTMLElement as we iterate through. |
|
// ielements from the line above. 'score' is the score of the line |
|
let i, e, score, rows = [], tbody = document.createElement("tbody"); |
|
|
|
// This is some hot new es6 magic right here. Remember how ielements turned our elements |
|
// array into an array of 2-element arrays, each containing the index and element |
|
// from the original array? Well, now we're unpacking that shit. You could implement |
|
// the same behavior using a simple counter variable starting at 0 and ticking up by |
|
// one with each iteration. I just find this approach more elegant and closer to the |
|
// way I would do it in Python. At the start of each iteration in this for loop, 'i' will |
|
// be the index and 'e' will be the table row element we want to examine. See |
|
// Destructuring assignment https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment |
|
for ([i, e] of ielements) { |
|
|
|
// Each news entry is represented as 3 consecutive 'tr' elements and the middle tr contains the sweet, juicy |
|
// score value we want to sort by. So, as we iterate over all the elements, we'll skip the 0th, take |
|
// the 1st, skip the 2nd, skip the 3rd, take the 4th. But what function represents the selection we |
|
// want to make? Mod division n % d gives you the remainder of n / d. Watch what happens: |
|
// 0 % 3 = 0 |
|
// 1 % 3 = 1 |
|
// 2 % 3 = 2 |
|
// 3 % 3 = 0 |
|
// 4 % 3 = 1 |
|
// 5 % 3 = 2 |
|
// 6 % 3 = 0 |
|
// Doing a mod division by 3 for all the natural numbers creates a repeating cycle of 0, 1, 2 for all the |
|
// numbers going on forever. In fact, replace 3 for n and the cycle will always be 0, 1, 2... up to n - 1. |
|
// Since we always want the middle element out of a set of 3, we want to take the element each time |
|
// the counter mod 3 equals 1. |
|
if (i % 3 === 1) { |
|
|
|
// So we're inside this if statement, so 'e' here must be one of those middle elements we were |
|
// looking for. We find the element with the css class of 'score' within our element, 'e' and |
|
// assign it to the variable scoreTag. Remember that querySelectorAll returns an Array, so |
|
// we index into it at its zeroth position to take what we assume is to be the only element in |
|
// the array. Which we can safely assume it will be, unless HN changes their layout. |
|
let scoreTag = e.querySelectorAll(".score")[0]; |
|
|
|
// Now that we have the score element, we need to extract the raw integer value of the score so that |
|
// we can sort the rows based on it. There are 2 big concepts introduced on this line. Ternary |
|
// Expressions and Regular Expressions (regexes). This line is dense. |
|
|
|
// A ternary expression is shorthand for doing a simple variable assignment based on an |
|
// if statement. It follows the format: |
|
// result = (condition)?expr1:expr2 |
|
// where condition is evaluated as a boolean true/false value and if it is true, then result will get |
|
// the value of expr1, otherwise result will be expr2. |
|
// We know that scoreTag will either be an HTMLElement or undefined. HTMLElement will evaluate to true, |
|
// undefined to false. If true, we're saying here we want to parse an integer out of whatever our regex |
|
// matched when executed on our scoreTag's innerText (the visible score text, ie. "27 points"). |
|
// Otherwise, we assume a score of 0. This places any abnormal content we may encounter at the bottom |
|
// of the list. But the 'abnormal content' is hypothetical and should never actully exist. |
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator |
|
|
|
// Okay now regexes are cool as shit. It's a whole language (or more broadly outside of js, a collection |
|
// of languages) that describe how to parse text and extract information. Like, grab all the digits |
|
// or find all words that start with a certain prefix within some text. Can't type it all here, |
|
// if you read any of these links, read the shit out of this one. It's insanely powerful. You will |
|
// be capable of things that mere mortals will worship you for. |
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions |
|
|
|
// Putting it together now, the parseInt function converts a string to an integer given some base. |
|
// In this case, being that we're normal humans, we want to use base 10. Yes it is mandatory to specify that. |
|
// So, we're defining the regex, /\d+/ which says grab the first sequential set of digits. If we executed that |
|
// on scoreTag.innerText, and the text was "27 points", we'd have the string "27". So, parseInt would |
|
// process that string and return the number 27 and that's what would be our score. Phew. |
|
let score = scoreTag ? parseInt(/\d+/.exec(scoreTag.innerText)[0], 10) : 0; |
|
|
|
// Now that we have a score with a numeric value, we could sort a list of rows on that value. |
|
// Lets begin by building that collection of rows with their score. |
|
// We defined a 'rows' variable above as an empty array []. Now, we'll push arrays to it that contain |
|
// the score and an array of the 3 table rows that make up this news headline. |
|
// Note, we're indexing into the original elements (not ielements). |
|
rows.push([score, [elements[i - 1], elements[i], elements[i + 1]]]); |
|
} |
|
} |
|
|
|
// Now we delete the table body with all the table rows in it. |
|
document.querySelectorAll(".itemlist tbody")[0].remove(); |
|
|
|
// The variable we created, tbody, is an HTMLElement living in memory in JS, |
|
// it needs to be actually appended to the HTML document so we can see it. |
|
// That whole living in memory thing really means its not attached to the |
|
// Document Object Model, or DOM. This line brings it into the DOM |
|
// by appending it as a child to the element with the class itemlist. |
|
// (The same place the last tbody lived before we removed it) |
|
document.querySelectorAll(".itemlist")[0].appendChild(tbody); |
|
|
|
// In some languages, sorting is not a huge pain in the ass. In JS, it just is... |
|
|
|
// rows.sort((a, b) => {b[0] - a[0]}) |
|
// This line is sorting the rows we built on line 92 based on their score values. |
|
// Sorting in JS is lexicographic by default and isn't smart enough to figure out |
|
// you're giving it numbers, so it will interpret your numbers as strings and |
|
// sort them as such by default. So instead we pass a custom compare function in as |
|
// an argument to rows.sort. This function is written out here as an es6 arrow function. |
|
// These compare functions are supposed to take to elements from the array being |
|
// sorted and return a boolean that determines sort order for the elements, or something. |
|
// Good one to read up on. |
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort |
|
for ([score, elements] of rows.sort((a, b) => { return b[0] - a[0]; })) { |
|
|
|
// Okay so we're walking through the sorted news headlines now, each one is a collection of 3 |
|
// HTMLElements, so we iterate each of those and append them to our table body. |
|
for (let ele of elements) { |
|
if (ele) tbody.appendChild(ele); |
|
} |
|
} |
|
|
|
// Finally, we iterate over the leftover elements and append them to the tbody. |
|
for (let ele of leftovers) { |
|
if (ele) tbody.appendChild(ele); |
|
} |