Skip to content

Instantly share code, notes, and snippets.

@JeffreyMercado
Last active May 15, 2019 18:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JeffreyMercado/63a853d49abba832fbc14aed6c249af2 to your computer and use it in GitHub Desktop.
Save JeffreyMercado/63a853d49abba832fbc14aed6c249af2 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @version 0.2
// @name Stack Exchange Timeline/Revisions links
// @description Adds timeline and revisions links to all applicable Stack Exchange posts
// @author Jeff Mercado
// @namespace https://stackoverflow.com/users/390278/jeff-mercado
// @include http*://stackoverflow.com/*
// @include http*://*.stackoverflow.com/*
// @include http*://askubuntu.com/*
// @include http*://*.askubuntu.com/*
// @include http*://superuser.com/*
// @include http*://*.superuser.com/*
// @include http*://serverfault.com/*
// @include http*://*.serverfault.com/*
// @include http*://mathoverflow.net/*
// @include http*://*.mathoverflow.net/*
// @include http*://stackapps.com/*
// @include http*://*.stackapps.com/*
// @include http*://*.stackexchange.com/*
// @exclude http*://chat.*.com/*
// ==/UserScript==
// this serves only to avoid embarassing mistakes caused by inadvertently loading this script onto a page that isn't a Stack Exchange page
let isSEsite = false;
for (let s of document.querySelectorAll('script')) isSEsite = isSEsite||/StackExchange\.ready\(/.test(s.textContent);
// don't bother running this if this isn't a scripted SE page
if (!isSEsite || typeof StackExchange === 'undefined' ) {
return;
}
function with_jquery(f) {
let script = document.createElement('script');
script.type = 'text/javascript';
script.textContent = `
if (window.jQuery) (${f.toString()})(window.jQuery);
//# sourceURL=${encodeURI(GM_info.script.namespace.replace(/\/?$/, '/'))}${encodeURIComponent(GM_info.script.name)}
`; // make this easier to debug
document.body.appendChild(script);
}
with_jquery(($) => {
function main() {
let links = [
new Link('timeline', 'Δ'),
new Link('revisions', 'ℜ')
];
$(() => {
let pageHandler = getPageHandler(document);
if (!pageHandler) return;
pageHandler.getPosts().each(function() {
let postHandler = getPostHandler(pageHandler, this);
if (!postHandler) return;
postHandler.addLinks(links);
});
});
}
function getPageHandler(container) {
if ($('body', container).hasClass('question-page')) return new QuestionPageHandler(container);
return null;
}
function getPostHandler(pageHandler, container) {
if ($(container).hasClass('question')) return new QuestionPostHandler(pageHandler, container);
if ($(container).hasClass('answer')) return new AnswerPostHandler(pageHandler, container);
return null;
}
class Link {
constructor(name, shortName) {
this.name = name;
this.shortName = shortName;
}
getUrl(id) {
return `/posts/${id}/${this.name}`;
}
}
class PageHandler {
constructor(container) {
this.container = container;
}
getPosts() {
return $(this.container).find(this.postSelector);
}
getLabel(link) {
return link.name;
}
}
class QuestionPageHandler extends PageHandler {
constructor(container) {
super(container);
}
get postSelector() {
return 'div.question, div.answer';
}
}
class PostHandler {
constructor(pageHandler, container) {
this.pageHandler = pageHandler;
this.container = container;
}
getId() {
return $(this.container).data(this.idSelector);
}
addLinks(links) {
let editLink = $(this.container).find('.edit-post, .suggest-edit-post, *[id^="edit-pending-"]');
let separator = editLink.prev('.lsep');
let lastLink = editLink;
for (let link of links) {
if (this.shouldInsertLink(link)) {
let newLink = this.createLink(link);
lastLink.after(separator.clone(), newLink);
lastLink = newLink;
}
}
}
shouldInsertLink(link) {
if (link.name === 'revisions' && this.hasModifications()) {
return false;
}
return true;
}
hasModifications() {
// if modified, there will be an extra post signature, the edit signature
return this.getContent().find('.post-signature').length > 1;
}
getContent() {
return $(this.contentSelector, this.container);
}
createLink(link) {
return $('<a></a>', {
href: link.getUrl(this.getId()),
class: `${link.name}-link`,
title: this.getTitle(link),
text: this.getLabel(link)
});
}
getTitle(link) {
return `view the ${link.name} for this ${this.containerName}`;
}
getLabel(link) {
return this.pageHandler.getLabel(link);
}
}
class QuestionPostHandler extends PostHandler {
constructor(pageHandler, container) {
super(pageHandler, container);
}
get containerName() {
return 'post';
}
get idSelector() {
return 'questionid';
}
get contentSelector() {
return '.postcell';
}
}
class AnswerPostHandler extends PostHandler {
constructor(pageHandler, container) {
super(pageHandler, container);
}
get containerName() {
return 'answer';
}
get idSelector() {
return 'answerid';
}
get contentSelector() {
return '.answercell';
}
}
main();
});
@CollinChaffin
Copy link

FYI, this won't be automatically installable to test in Tampermonkey unless you rename this to .user.js. Otherwise it is not detected properly and leaves it to the user to paste into their own local script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment