Skip to content

Instantly share code, notes, and snippets.

@DavidJCobb
Created July 13, 2024 03:58
Show Gist options
  • Save DavidJCobb/7f2ffaec4a8a0cc1d9b31a2b8eadf8a0 to your computer and use it in GitHub Desktop.
Save DavidJCobb/7f2ffaec4a8a0cc1d9b31a2b8eadf8a0 to your computer and use it in GitHub Desktop.
The Old New Thing - userscript for fixes

Userscript to fix issues on Raymond Chen's blog, and improve the browsing experience:

  • Redirect old-format links within the blog as close to their present-day destinations as possible
  • Add pagination to the top, and allow pagination between months (helps when reading in chronological order, month by month, on mobile)

Might expand this into a full WebExtension later? There are some articles that used to have inline JavaScript which now just renders as plain-text; recreating these scripts within a WebExtension (no eval!!) could be useful.

// ==UserScript==
// @name The Old New Thing - Tweaks
// @version 1
// @grant none
// @run-at document-start
// @include https://devblogs.microsoft.com/oldnewthing/*
// ==/UserScript==
// URL_MATCHERS[hostname] == [ pathname_regex, pathname_regex, ... ]
const OLD_URL_MATCHERS = new Map([
["weblogs.asp.net", [
/^[\/\\]+oldnewthing[\/\\]+archive[\/\\]+(?<year>\d{4})[\/\\]+(?<month>\d\d)[\/\\]+(?<day>\d\d)[\/\\]+\d+\.aspx$/,
]],
["devblogs.microsoft.com", [
/^[\/\\]+oldnewthing[\/\\]+archive[\/\\]+(?<year>\d{4})[\/\\]+(?<month>\d\d)[\/\\]+(?<day>\d\d)[\/\\]+\d+\.aspx$/i,
]]
]);
function url_for_date(month, day, year) {
day = day.toString().padStart(2, "0");
month = month.toString().padStart(2, "0");
return `https://devblogs.microsoft.com/oldnewthing/${year}${month}${day}-00/`;
}
function url_for_month(month, year) {
month = month.toString().padStart(2, "0");
return `https://devblogs.microsoft.com/oldnewthing/${year}/${month}/`;
}
function upgrade_url(url) {
// Observed formats:
// http://weblogs.asp.net/oldnewthing/archive/2004/01/30/65013.aspx
// https://devblogs.microsoft.com/oldnewthing/archive/2004/02/03/66660.aspx
// https://devblogs.microsoft.com/oldnewthing/20040203-00/?p=40763
if (!(url instanceof URL))
url = new URL(url);
let hostname = url.hostname.toLowerCase();
let pathname = url.pathname;
for(const [key, list] of OLD_URL_MATCHERS.entries()) {
if (hostname != key)
continue;
for(let regex of list) {
let match = pathname.match(regex);
if (match)
return url_for_date(+match.groups.month, match.groups.day, match.groups.year);
}
}
return null;
}
function upgrade_link(node) {
let upgraded = upgrade_url(node.href);
if (upgraded !== null) {
node.setAttribute("data-orig-url", node.href);
node.href = upgraded;
}
}
function enhance_pagination() {
let container = document.getElementById("primary");
if (!container)
return;
let pagination = container.querySelector(":scope>nav.pagination-container");
if (!pagination)
return;
//
// Add pagination between months.
//
let this_date_match = window.location.pathname.match(/[\\\/]+(?<year>\d{4})[\\\/]+(?<month>\d\d)(?:[\\\/]+page[\\\/]+\d+[\\\/]*)?$/);
if (this_date_match) {
let month = +this_date_match.groups.month;
let year = +this_date_match.groups.year;
let ul = pagination.querySelector("ul");
let first = ul.firstElementChild;
let last = ul.lastElementChild;
let add_future_month = false;
let add_past_month = false;
if (first.classList.contains("active")) {
add_future_month = true;
if (first == last) {
add_past_month = true;
}
} else if (last.classList.contains("active")) {
add_past_month = true;
}
if (add_future_month || add_past_month) {
let archives = document.querySelector("ul.ftr-archives");
if (archives) {
let format = new Intl.DateTimeFormat(undefined, { year: "numeric", month: "long" });
function _format(month, year) {
let date = new Date(year, month - 1);
return format.format(date);
}
if (add_future_month) {
let dm = month;
let dy = year;
if (dm == 12) {
dm = 1;
++dy;
} else {
++dm;
}
let newest = archives.firstElementChild.querySelector("a").href.match(/[\\\/]+(?<year>\d{4})[\\\/]+(?<month>\d\d)[\\\/]*$/);
if (newest) {
let ny = +newest.groups.year;
let nm = +newest.groups.month;
if (dy < ny || (dy == ny && dm <= nm)) {
let li = document.createElement("li");
let a = document.createElement("a");
li.className = "page-item";
a.href = url_for_month(dm, dy);
a.textContent = _format(dm, dy);
a.className = "page-link";
/*
let chevron = document.createElement("i");
chevron.className = "fabric-icon fabric-icon--ChevronLeft";
a.insertBefore(chevron, a.firstChild);
//*/
li.append(a);
ul.insertBefore(li, ul.firstElementChild);
}
}
}
if (add_past_month) {
let dm = month;
let dy = year;
if (dm == 1) {
dm = 12;
--dy;
} else {
--dm;
}
let oldest = archives.lastElementChild.querySelector("a").href.match(/[\\\/]+(?<year>\d{4})[\\\/]+(?<month>\d\d)[\\\/]*$/);
if (oldest) {
let oy = +oldest.groups.year;
let om = +oldest.groups.month;
if (dy > oy || (dy == oy && dm >= om)) {
let li = document.createElement("li");
let a = document.createElement("a");
li.className = "page-item";
a.href = url_for_month(dm, dy);
a.textContent = _format(dm, dy);
a.className = "page-link";
/*
let chevron = document.createElement("i");
chevron.className = "fabric-icon fabric-icon--ChevronRight";
a.append(chevron);
//*/
li.append(a);
ul.append(li);
}
}
}
}
}
}
// Add top-side pagination.
container.insertBefore(pagination.cloneNode(true), container.firstElementChild);
}
if (document.readyState == "loading") {
let OBSERVER = new MutationObserver(function(records, observer) {
for(const record of records) {
for (const node of record.addedNodes) {
if ((node.nodeName == "A" || node.nodeName == "a") && !node.classList.contains("page-link")) {
upgrade_link(node);
continue;
}
}
}
});
OBSERVER.observe(document.documentElement, {
subtree: true,
childList: true,
});
document.addEventListener("DOMContentLoaded", function() {
OBSERVER.disconnect();
enhance_pagination();
});
} else {
document.querySelectorAll("a[href]").forEach(function(node) {
upgrade_link(node);
});
enhance_pagination();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment